Implement API first strategy with OpenAPI generator plugin

Rui Zhou
Javarevisited
Published in
4 min readApr 8, 2024
Photo by JuniperPhoton on Unsplash

Everyone is talking about API first strategy nowadays, In this article I will demo how we exactly implement API first strategy with OpenAPI generator plugin in a real-world solution.

Strategy

API-first, also called the API-first approach, prioritizes APIs at the beginning of the software development process, positioning APIs as the building blocks of software. API-first organizations develop APIs before writing other code, instead of treating them as afterthoughts. This lets teams construct applications with internal and external services that are delivered through APIs.

Advantages of the API-first approach with a common OpenAPI Specification:

  • Ensures the use of the same contracts by all the parts of the system.
  • Facilitates cooperation between the backend and the frontend, facilitating parallel development.
  • Automates generation of repeatable elements like models, services and controllers, reducing the amount of boilerplate code, organizing the structure and saving developers’ time.
  • Automatically generates and enables API documentation.

Diagram

API first approach

As seen from this picture, before developing the application, at the API design stage, we will create OpenAPI spec(e.g. a yaml file) and store it as a single source in the git repo. At this stage, the API designer, such as architect, BA, developer, they need to contribute to this API spec document and do a proper review, then push it to git repo and trigger the CICD process.

CICD will validate the API spec, if the spec is incompatible with the previous version then we need to create a newer version and commit again. otherwise, the spec will be uploaded to an artifact store such as Jfrog Artifactory or Nexus. In this demo, I don’t have artifactory, hence I use bitbucket as the storage instead.

At the microservice development stage, developer will use the openapi generator plugin(maven/gradle), to import the API spec from remote url(e.g. bitbucket or jfrog artifactort), and generate server/client code from the API spec.

We can also customize the openapi generator plugin, parse the api spec configuration to export API dependency information. such as:

  • microservice A has dependency of microservice B v1 api, microservice C v2 api
  • microservice B has dependency of microservice C v3 api

with these information we can create application catalog, inventory, … etc.

Generate code with the OpenAPI generator plugin

When a microservice wants to create an API server/client, just needs to have this configuration in its maven POM.xml:

<build>        
<plugins>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator.version}</version>
<executions>
<execution>
<id>petstore-client</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<auth></auth>
<!-- <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>-->
<inputSpec>https://raw.githubusercontent.com/softarts/apifirst/main/apifirst-contracts/retail/petstore/v1.yaml</inputSpec>
<generatorName>java</generatorName>
<skipIfSpecIsUnchanged>true</skipIfSpecIsUnchanged>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<!-- <generateSupportingFiles>false</generateSupportingFiles>-->
<configOptions>
<!-- <sourceFolder>src/gen/java/main</sourceFolder>-->
<interfaceOnly>true</interfaceOnly>
<dateLibrary>java8</dateLibrary>
<delegatePattern>true</delegatePattern>
<useTags>true</useTags>
<requestMappingMode>api_interface</requestMappingMode>
<library>resttemplate</library>
<generateClientAsBean>true</generateClientAsBean>
<useSpringBoot3>true></useSpringBoot3>
<useJakartaEe>true</useJakartaEe>
<apiPackage>com.zhourui.retail.petstore.consumer.api.v1</apiPackage>
<modelPackage>com.zhourui.retail.petstore.model.v1</modelPackage>
<serializableModel>true</serializableModel>
</configOptions>
</configuration>
</execution>
<execution>
<id>petstore-server</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<!-- <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>-->
<auth></auth>
<inputSpec>https://raw.githubusercontent.com/softarts/apifirst/main/apifirst-contracts/retail/petstore/v1.yaml</inputSpec>
<generatorName>spring</generatorName>
<skipIfSpecIsUnchanged>true</skipIfSpecIsUnchanged>
<configOptions>
<!-- <sourceFolder>src/gen/java/main</sourceFolder>-->
<validateSpec>false</validateSpec>
<interfaceOnly>true</interfaceOnly>
<dateLibrary>java8</dateLibrary>
<delegatePattern>false</delegatePattern>
<useTags>true</useTags>
<requestMappingMode>api_interface</requestMappingMode>
<generateClientAsBean>true</generateClientAsBean>
<useSpringBoot3>true></useSpringBoot3>
<useJakartaEe>true</useJakartaEe>
<apiPackage>com.zhourui.retail.petstore.producer.api.v1</apiPackage>
<modelPackage>com.zhourui.retail.petstore.model.v1</modelPackage>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

The full parameters list are at the openapi repo. here I just highlight some important stuff:

API spec

it use github repo to store the API spec(see here), and this is the single source of API spec. all participants need to create their code from this spec.

<inputSpec>https://raw.githubusercontent.com/softarts/apifirst/main/apifirst-contracts/retail/petstore/v1.yaml</inputSpec>

<auth>

This is the auth header when need to access API spec file which resides at remote url(e.g. different bitbucket repo). First of all, create a personal token at bitbucket account management; then encode it in base64.

bitbucket_username:bitbucket_token
=> base64 encode
Yml0YnVja2V0X3VzZXJuYW1lOmJpdGJ1Y2tldF90b2tlbg==

the <auth> will be:

<auth>Authorization:Basic Yml0YnVja2V0X3VzZXJuYW1lOmJpdGJ1Y2tldF90b2tlbg==</auth>

multi languages

openapi generator supports multiple languages, usually I will use

  • java(generator) resttemplate(library) => java-client, use resttemplate as http client
  • kotlin-spring(generator) => kotlin-spring server

Implement service with few lines of code

OpenAPI generator maven plugin will generate model, consumer, producer API files under the target folder.

controller(server)

Just implements from the generated API(StoreApi ) and copy the function declaration:

import com.zhourui.retail.petstore.producer.api.v1.StoreApi;

@RestController
public class StoreController implements StoreApi {
@Override
public ResponseEntity<Map<String, Integer>> getInventory() {
// your code
Map<String, Integer> m = Map.of(
"sugar", 1000,
"wheat", 250
);
return new ResponseEntity<>(m, HttpStatus.OK);
}
}

client

use bean configuration to set server url

@Configuration
@Import(
value = {StoreApi.class, com.zhourui.retail.petstore.consumer.api.ApiClient.class}
)
public class BaseConfiguration {
@Value("${ApiFirstDemo.API.PetStoreUrl}")
private String petStoreUrl;

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

@Bean
public StoreApi storeApi() {
StoreApi storeApi = new StoreApi();
storeApi.getApiClient().setBasePath(petStoreUrl);
return storeApi;
}
}

the URL is in application.yaml (use default value, in production it can be rendered by CICD)

ApiFirstDemo:
API:
PetStoreUrl: ${PetStoreUrlV1:http://localhost:8080/api/v3}

and usage

@RestController
public class DemoController {
@Autowired
private StoreApi storeApi;

@GetMapping(value = "/clientdemo")
public ResponseEntity<Map<String, Integer>> clientDemo() {
Map<String, Integer> m = storeApi.getInventory();
return new ResponseEntity<>(m,OK);
}
}

Testing

curl -X GET "http://localhost:8080/clientdemo"
# => will hit its own endpoint "storeapi"
{"wheat":250,"sugar":1000}

Reference

Happy Coding!

--

--