Autoupdating controllers layer based on OpenAPI contract - lstefaniszyn/e-bank_ GitHub Wiki

Autoupdating controllers layer based on OpenAPI contract

Table of Contents

About

Applying documentation first approach, we decided to use OpenAPI Specification to define an API contract (openapi.yaml) and consider this file as our primary, preferably the only, source of API structure. To provide the most efficient and consistent way of implementing controllers layer, we took advantage of OpenAPI tools to auto-generate controller interfaces and data transfer objects (DTOs) based on defined OpenAPI contract.

Auto-generated files

How to trigger auto-generation?

To trigger auto-generation you need to compile the project

mvn clean compile

Where generated files are located?

Auto-generated files are located in /target/generated-sources/openapi/src/main/java path.

The Api interfaces are stored in package com.example.ebank.generated.api and DTOs in com.example.ebank.generated.dto.

What kind of files are generated?

Api interfaces

For each tag defined in openapi.yaml the Api interface is generated. Each interface contains all endpoints defined as controller methods with Swagger annotations.

For instance the below path in OpenAPI contract

"/api":
    get:
        tags:
            - app
        summary: Get API status
        operationId: getStatus
        responses:
            "200":
                description: successful operation
                content:
                    application/json:
                        schema:
                            $ref: "#/components/schemas/AppStatus"
            "400":
                description: Error
                content: {}
            "404":
                description: Error
                content: {}

triggers generation of such an Api interface

@Api(value = "App", description = "the App API")
public interface AppApi {

    /**
     * GET /api : Get API status
     *
     * @return successful operation (status code 200)
     *         or Error (status code 400)
     *         or Error (status code 404)
     */
    @ApiOperation(value = "Get API status", nickname = "getStatus", notes = "", response = AppStatusDto.class, tags={ "app", })
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "successful operation", response = AppStatusDto.class),
        @ApiResponse(code = 400, message = "Error"),
        @ApiResponse(code = 404, message = "Error") })
    @RequestMapping(value = "/api",
        produces = { "application/json" },
        method = RequestMethod.GET)
    ResponseEntity<AppStatusDto> getStatus();

}

Data Transfer Objects

DTO is generated for each schema defined in openapi.yaml.

For instance the below schema in OpenAPI contract

AppStatus:
    type: object
    properties:
        app-version:
            type: string
            description: https://schema.org/version

triggers generation of such an object

public class AppStatusDto   {
  @JsonProperty("app-version")
  private String appVersion;

  public AppStatusDto appVersion(String appVersion) {
    this.appVersion = appVersion;
    return this;
  }

  /**
   * https://schema.org/version
   * @return appVersion
  */
  @ApiModelProperty(value = "https://schema.org/version")

  {...}
}

How to work with generated files?

To implement logic behind the endpoints, you need to create a RestController which implement specific Api interface.

For instance, for the AppApi interface

@Api(tags = "app")
@RestController
public class AppStatusController implements AppApi {

    @Value("${app.version}")
    private final String appVersion = "1.0.0";

    @Override
    public ResponseEntity<AppStatusDto> getStatus() {
        AppStatusDto dto = new AppStatusDto();
        dto.setAppVersion(appVersion);
        return ResponseEntity.ok(dto);
    }
}

For more complicated endpoints, it is important to map entities to generated Data Transfer Objects. It can be done easily using MapStruct which generates mapping between similar objects. To see example mappers go to com/example/ebank/mappers.

Maven configuration

Below you can find required dependency for OpenAPI tools and configured maven plugin

<dependency>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator</artifactId>
    <version>${openapitools-version}</version>
</dependency>
<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>${openapitools-version}</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <generatorName>spring</generatorName>
                <inputSpec>${project.basedir}/docs/api/openapi.yml</inputSpec>
                <configOptions>
                    <sourceFolder>src/main/java</sourceFolder>
                    <library>spring-boot</library>
                    <dateLibrary>java11</dateLibrary>
                    <java11>true</java11>
                    <interfaceOnly>true</interfaceOnly>
                    <skipDefaultInterface>true</skipDefaultInterface>
                    <useTags>true</useTags>
                </configOptions>
                <modelPackage>com.example.ebank.generated.dto</modelPackage>
                <modelNameSuffix>Dto</modelNameSuffix>
                <apiPackage>com.example.ebank.generated.api</apiPackage>
                <generateSupportingFiles>false</generateSupportingFiles>
            </configuration>
        </execution>
    </executions>
</plugin>

Key configuration properties

  • configOptions.interfaceOnly=true - only Api interface is generated

  • configOptions.skipDefaultInterface=true - methods in Api interface do not have default implementation

  • configOptions.useTags=true - generated Api interfaces corresponds to defined tags

  • generateSupportingFiles=false - no supporting files are generated (only api and model)

⚠️ **GitHub.com Fallback** ⚠️