Skip to main content

When working with microservices architecture, one of the most important aspects is inter-service communication. Usually, each microservice stores data in its own database, and if we follow the MVC design pattern, we probably have model classes that map the relational database to object models, and components that contain methods for performing CRUD operations. These components are exposed by controller endpoints.

So that one microservice calls another, the caller needs to know the exact request and response model classes. This article will show a simple example of how to generate such models with SpringDoc OpenAPI.

I will create two services that will provide basic CRUD operations. For demonstrating purposes I chose to store data about vehicles:

  • vehicle-manager- the microservice that provides vehicles’ data to the client
  • vehicle-manager-client – the client microservice that requests vehicles’ data

For the purpose of this tutorial, I created empty Spring Boot projects via SpringInitializr.

In order to use the OpenAPI in our Spring Boot project, we need to add the following Maven dependency in our pom file:

<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-ui</artifactId>
  <version>1.5.5</version>
</dependency>

In the vehicle-manager microservice I created a Vehicle class that looks like this:

@Data
@Builder
@Schema(name = "Vehicle", description = "Example vehicle schema")
public class Vehicle {
    private VehicleType vehicleType;
    private String registrationPlate;
    private int seatsCount;
    private Category category;
    private double price;
    private Currency currency;
    private boolean available;
}

And a controller:

package com.n47.vehiclemanager.ctrl;

import com.n47.vehiclemanager.model.Vehicle;
import com.n47.vehiclemanager.service.VehicleService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Tag(name = "vehicle", description = "Vehicle controller API")
@RestController
@RequiredArgsConstructor
@RequestMapping(path = "/vehicle")
public class VehicleCtrl {

    private final VehicleService vehicleService;

    @PostMapping(path = "/add")
    public void addVehicle(@RequestBody @Valid Vehicle vehicle) {
        vehicleService.addVehicle(vehicle);
    }

    @GetMapping(path = "/get")
    public Vehicle getVehicle(@RequestParam String registrationPlate) throws Exception {
        return vehicleService.getVehicle(registrationPlate);
    }
}

The important annotations here from openAPI are @Schema and @Tag. The former is used to define the actual class that needs to be included in the API documentation. The latter is used for grouping operations, such as all methods under one controller.

The swagger documentation interface for Vehiclemanager microservice is shown on Figure 1, and can be accessed on the following links:

If we open http://localhost:8080/api-docs in our browser (or any other port we set our Spring boot app to run on), we can get the entire documentation for the Vehiclemanager microservice. The important part for the model generation is right under components/schemas, while the controller endpoints are under paths.

{
   "openapi":"3.0.1",
   "info":{
      "title":"OpenAPI definition",
      "version":"v0"
   },
   "servers":[
      {
         "url":"http://localhost:8080",
         "description":"Generated server url"
      }
   ],
   "tags":[
      {
         "name":"vehicle",
         "description":"Vehicle controller API"
      }
   ],
   "paths":{
      "/vehicle/add":{
         "post":{
            "tags":[
               "vehicle"
            ],
            "operationId":"addVehicle",
            "requestBody":{
               "content":{
                  "application/json":{
                     "schema":{
                        "$ref":"#/components/schemas/Vehicle"
                     }
                  }
               },
               "required":true
            },
            "responses":{
               "200":{
                  "description":"OK"
               }
            }
         }
      },
      "/vehicle/get":{
         "get":{
            "tags":[
               "vehicle"
            ],
            "operationId":"getVehicle",
            "parameters":[
               {
                  "name":"registrationPlate",
                  "in":"query",
                  "required":true,
                  "schema":{
                     "type":"string"
                  }
               }
            ],
            "responses":{
               "200":{
                  "description":"OK",
                  "content":{
                     "*/*":{
                        "schema":{
                           "$ref":"#/components/schemas/Vehicle"
                        }
                     }
                  }
               }
            }
         }
      }
   },
   "components":{
      "schemas":{
         "Vehicle":{
            "type":"object",
            "properties":{
               "vehicleType":{
                  "type":"string",
                  "enum":[
                     "MOTORBIKE",
                     "CAR",
                     "VAN",
                     "BUS",
                     "TRUCK"
                  ]
               },
               "registrationPlate":{
                  "type":"string"
               },
               "seatsCount":{
                  "type":"integer",
                  "format":"int32"
               },
               "category":{
                  "type":"string",
                  "enum":[
                     "A",
                     "B",
                     "C",
                     "D",
                     "E"
                  ]
               },
               "price":{
                  "type":"number",
                  "format":"double"
               },
               "currency":{
                  "type":"string",
                  "enum":[
                     "EUR",
                     "USD",
                     "CHF",
                     "MKD"
                  ]
               },
               "available":{
                  "type":"boolean"
               }
            },
            "description":"Example vehicle schema"
         }
      }
   }
}

I am going to create a Vehiclemanager-client service, running on port 8082, that will get vehicle information for a given registration plate, by calling the Vehiclemanager microservice. In order to do so, we need to generate the Vehicle model class defined in the original Vehicle microservice. We can generate it by adding the swagger codegen plugin in the pom’s plugins section, in the new demo service, like this:

<profiles>
  <profile>
	<id>generateModels</id>
	<build>
	  <plugins>
		<plugin>
	      <groupId>io.swagger.codegen.v3</groupId>
			<artifactId>swagger-codegen-maven-plugin</artifactId>
			<version>3.0.11</version>
			<configuration>
		      <output>${project.basedir}</output>
			  <inputSpec>default-config</inputSpec>
			  <language>java</language>
			  <generateModels>true</generateModels>
			  <generateModelDocumentation>false</generateModelDocumentation>
			  <generateApis>false</generateApis>
			  <generateApiTests>false</generateApiTests>
			  <generateModelTests>false</generateModelTests>
			  <generateSupportingFiles>false</generateSupportingFiles>
			  <configOptions>
			    <sourceFolder>src/main/java</sourceFolder>
				<hideGenerationTimestamp>true</hideGenerationTimestamp>
				<sortParamsByRequiredFlag>true</sortParamsByRequiredFlag>
				<checkDuplicatedModelName>true</checkDuplicatedModelName>
				<useBeanValidation>true</useBeanValidation>
				<library>feign</library>
				<dateLibrary>java8-localdatetime</dateLibrary>
			  </configOptions>
			</configuration>
			<executions>
			  <execution>
				<id>generate-vehiclemanager-classes</id>
				<goals>
			      <goal>generate</goal>
				</goals>
				<configuration>
				  <inputSpec>http://localhost:8080/api-docs</inputSpec>
				  <language>java</language>
				  <modelPackage>com.n47.domain.external.model</modelPackage>
				  <modelsToGenerate>Vehicle</modelsToGenerate>
				</configuration>
			  </execution>
			</executions>
		  </plugin>
		</plugins>
	 </build>
  </profile>
</profiles>

After running the corresponding maven profile with:

> mvn clean compile -P generateModels

the models defined in <modelsToGenerate> tag will be created under the specified package in <modelPackage> tag.

Codegen generates for us the entire model class with all classes that are defined inside it.

It is important to note that we can have models generated from different services. In each execution (see line 30 from the XML snippet) we can define the corresponding API documentation link in the <inputSpec> tag (line 37).

To demo data transfer from Vehiclemanager to Vehiclemanager-client microservice, we can send a simple request via Postman. The request I am going to use will be a GET request, that accepts a parameter registrationPlate which is used to query the vehicles stored in the Vehiclemanager microservice. The response is shown in Figure 3, which is a JSON containing the vehicle’s data that I hardcoded in the Vehiclemanager microservice.

Using OpenAPI helps us getting rid of copy-paste and boilerplate code, and more importantly, we have an automated mechanism that on each Maven clean compile generates the latest models from other microservices.

You can find the full code example microservices in the links below:

Feel free to download and run them yourself, and leave a comment or feedback.

Kristijan Iliev

Experienced software engineer, passionate about Cloud computing. kristijan.iliev@north-47.com LinkedIn

One Comment

  • Florent says:

    Hi,
    Thank you for the article.
    In 2019, you werr using API first devlopment with Swagger/OpenAPI.
    Why did you change to code first approach?
    Regards
    Florent