Introduction

In this blog, we will discuss developing REST APIs conforming to OpenApi Specifications and using the OpenApi code generator tool in Java & Spring boot environment to generate Rest controllers and domain models.

Before getting into the details, let's talk about how we can develop and expose a REST endpoint in a Spring Boot-based application. Let's say we want to expose a Rest Api for an employee management system that returns the list of all the employees in the system.

With Spring, it takes just a few steps to develop a fresh RESTful endpoint.

  • Create the Rest controllers and associated path-mapped methods with default actions.
@RestController
public class EmployeeController {

@Autowired
private EmployeeService employeeService;

    @GetMapping(path = "/employees/{employeeId")
    public ResponseEntity<Employee> employees(@PathVariable("employeeId") String employeeId) {
        return employeeService.findEmployeeById(employeeId);
     }
  • Create the domain classes as needed by the endpoint.
public class Employee   {
  @JsonProperty("id")
  private Long id;

  @JsonProperty("name")
  private String name;

  @NotNull
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  @NotNull
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

}
  • Implement an exception handler and map the exceptions to proper HTTP error codes.

That's pretty much it, you have a basic rest endpoint ready and functional. You can run this spring boot application, hit the endpoint http://localhost:8080/employees/1, and get the appropriate response, provided that you have implemented the service layer properly.

But while developing enterprise applications, things are not that simple. Most of the time these APIs are exposed to external systems, customers, or partners. In such cases, your API is a key interface between the end customers and your service, and a good API design plays an important role in determining customer satisfaction. The interface needs to be well thought, pass through a few design reviews and enhancement cycles, accommodate a few more requirements before freezing the contract.

This leads us to think in the direction of developing REST APIs in a design-first approach.

Design First Approach

With the design-first approach, we need to document the API specification in a human-readable format, which could simplify the complex business requirements in form of the endpoints and could be understood by business stakeholders as well. The specification should also be universally accepted as a standard for exchanging REST API contracts.

OpenApi Specification - Overview

This brings us to OpenApi specification, which was known as Swagger specification earlier.

As per the definition on the official website -

The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection.

Let's understand the specification from our earlier example of an employee management system. This system is supposed to expose APIs for creating and fetching employee resources. This is how the document will look like when written in OpenApi spec -

openapi: "3.0.3"
info:
  version: 1.0.0
  title: Employee Management API
servers:
  - url: http://employee-management.swagger.io/v1
paths:
  /employees:
    get:
      summary: List all employees
      operationId: listEmployees
      tags:
        - employees
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: A paged array of Employees
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:    
              schema:
                $ref: "#/components/schemas/Employees"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      summary: Create an Employee
      operationId: createEmployees
      tags:
        - employees
      responses:
        '201':
          description: Null response
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /employees/{EmployeeId}:
    get:
      summary: Info for a specific Employee
      operationId: showEmployeesById
      tags:
        - Employees
      parameters:
        - name: EmployeeId
          in: path
          required: true
          description: The id of the Employee to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Employee"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  schemas:
    Employee:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Employees:
      type: array
      items:
        $ref: "#/components/schemas/Employee"
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string

An easier way to comprehend this document is to read it at https://editor.swagger.io/

This document could greatly ease the discussion on expectations between stakeholders (technical or non-technical), prevent mistakes and misunderstandings right at the design stage, and speed up the development as the engineers get a business sign-off in form of this document.

The design part is done, What's next?

With the API design being ready in form of an OpenApi spec document, we can focus on the implementation part.

One of the great benefits of using universally accepted standards is that there is a wide range of dev tools contributed by the community, which are easy to adopt and reduce the overall development effort.

We are talking about OpenApi spec code generator here.

This code generator can parse an OpenApi spec document and generate the server stubs and client SDKs for a wide range of programming languages. To simplify this with an example, if you are a java developer who intends to develop and expose a REST endpoint in Spring boot application, all you need is the OpenApi spec document, feed it to the code generator, supply the choice of language and framework (java and spring-boot in this case), and get the Rest Controllers, model classes generated.

You can also integrate the code generation part with your application's build process by using the Maven or Gradle plugins for OpenApi code generation

Let's look at the implementation step by step

We will look at an example implementation where we will be using Maven for dependency management and Spring boot 2.6.2 with Java 17.

  • Update the pom.xml with appropriate dependencies as show in the below snippet.
<dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>jackson-databind-nullable</artifactId>
            <version>0.2.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.6.3</version>
        </dependency>
  • Add the openapi-generator-maven-plugin to the plugins section in the pom. Below is a snippet from the example we are building. I have tried to document the configuration options in form of comments.
<plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <!-- RELEASE_VERSION -->
                <version>5.3.1</version>
                <!-- /RELEASE_VERSION -->
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <!-- To print detailed logs during the plugin execution -->
                            <verbose>true</verbose>
                            <!-- <templateDirectory>${project.basedir}/src/main/resources/openapi-custom-template</templateDirectory> -->
                            <!-- Path to api contract file -->
                            <inputSpec>${project.basedir}/src/main/resources/spec/employee-management.yml</inputSpec>
                            <!-- Full list of all generators - https://openapi-generator.tech/docs/generators#server-generators -->
                            <generatorName>spring</generatorName>
                            <library>spring-boot</library>
                            <!-- Name of the package where handler, models and invokers should 
                                be generated -->
                            <apiPackage>${default.package}.handler</apiPackage>
                            <modelPackage>${default.package}.model</modelPackage>
                            <invokerPackage>${default.package}.handler</invokerPackage>
                            <configOptions>
                                <!-- Name of the source folder path under 'generated' folder -->
                                <sourceFolder>src/main/java</sourceFolder>
                                <!-- Set to true if you want to generate a delegate class, which 
                                    will be injected in controller -->
                                <delegatePattern>true</delegatePattern>
                                <!-- If set to true, generates only API controller interface and 
                                    you need to provide the implementation -->
                                <interfaceOnly>false</interfaceOnly>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Here is the reference to the full pom.xml

https://gist.github.com/nakulshukla08/27597d1eb3266f362f06256c63df2e7c
  • Run the maven build with command ./mvnw clean install. This will execute the openapi codegen plugin during the generate goal and server stub classes i.e controllers, models, configuration and spring boot main application will be generated in the directory as configured in the plugin. Here is a snapshot for reference -

image.png

Customization

With the above configuration, you should have a simple setup with the OpenApi code generator working.

But what about the use cases when the generated classes are missing a few details that you want them to have. The good news is that the code generation tooling is quite flexible and allows the users to customize the code generation with the help of custom generators and templating.

For most common use cases, customization via templating solves the purpose. Let's consider a use case where we want to protect one of our endpoints with the help of spring security annotation PreAuthorize, and want to have the flexibility of specifying the security role in the API contract yml file itself. This way, the engineer or architect who is designing the contract, has the liberty to make a few decisions on the security front as well, which gets passed to the developers for further implementation.

To implement this, we need to follow the below steps -

  • Identify the relevant Mustache file from the the OpenApi Github repository. In our case, we are using java-spring generator and we want to customize the api controller template, so the relevant file for us would be apiController.mustache under the directory JavaSpring. Here is the link that points to this specific file on Github.
  • We need to copy this file and paste it somewhere in our project, lets say we keep it at src/main/resources/openapi-custom-template.
  • Update the mustache file with the needed customization. In our case, we want to have a PreAuthorize annotation generated on top of the endpoint methods and with the role which we will pass in the contract yml file. We will be using vendorExtensions, which is an implicit object for the code generator. Here is what we will add in the mustache file -
{{#vendorExtensions.x-security-role}}
     @PreAuthorize("{{vendorExtensions.x-security-role}}")
    {{/vendorExtensions.x-security-role}}
    public {{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}},{{/-last}}{{/allParams}})

x-security-role is going to be the custom attribute that we will be using in api contract yml file. Any custom attribute with prefix x becomes accessible to vendorExtensions object during code generation.

Here is a gist that shows the complete mustache file after the update.

https://gist.github.com/nakulshukla08/167b6a91734acf848e86b38ec4971c35
  • Next, we need to use the custom attribute x-security-role in contract yml as shown below -
paths:
  /employees:
    get:
      summary: List all employees
      operationId: listEmployees
      x-security-role: employee-viewer
  • Now we need to update the code generator plugin configuration in pom.xml to point it to the custom template directory where we have kept the customized copy of apiController.mustache.
<templateDirectory>${project.basedir}/src/main/resources/openapi-custom-template</templateDirectory>
  • With all these changes, when you run the maven build on this project, you can see that the EmployeeApiController has the annotation PreAuthorize with role employee-viewer inside it.
 @PreAuthorize("employee-viewer")
    public ResponseEntity<List<Employee>> listEmployees(@ApiParam(value = "How many items to return at one time (max 100)") @Valid @RequestParam(value = "limit", required = false) Integer limit) {
        return delegate.listEmployees(limit);
    }

There are other ways of customizing the code generation while using open api code generator, such as writing a custom code generator by extending the original apis. Writing a custom code generator is a more involved process and should be done only when the requirement is significantly complex and cannot be addressed with templating.

Take Away

  • OpenApi Specification is an open standard for defining rest api contracts.

  • OpenApi code generator helps in speeding up the development and supporting a design-first approach.

  • OpenApi code generator allows customization of code generation by extending the templates, which we looked at in detail.

References

  • Complete Sample Project
  • OpenApi Specification - Official Documentation
  • OpenApi Code Generator - Official Documentation
  • OpenApi Code generator - github repository
Logo

ModelScope旨在打造下一代开源的模型即服务共享平台,为泛AI开发者提供灵活、易用、低成本的一站式模型服务产品,让模型应用更简单!

更多推荐