Swagger
We often use Swagger to generate REST API documentations and even REST client. However, if put all the API definition in a single Swagger .yaml file, it gets really messy as the application grows.
To have a clean Swagger configuration, first create a multiple module maven project, as follows:
project
- rest-api
- v1
common.yaml
pet.yaml
pet-img.yaml
store.yaml
user.yaml
petstore-api.yaml
pom.xml
- rest-client
- v1
pom.xml
pom.xml
- The
rest-apimodule will be used on the server side to implement the generated REST interfaces. - The
rest-clientmodule can be used in component tests or other services integrating with your service.
REST API generation
In the petstore-api.yaml file, we specify the service resources as:
openapi: '3.0.0'
info:
title: Pet Store API
description: Pet store
version: '0.1'
termsOfService: url
paths:
/pet/{petId}
$ref: 'pet.yaml#/api'
/pet/{petId}/images
$ref: 'pet-img.yaml#/api'
/store/{storeId}
$ref: 'store.yaml#/api'
/user/{username}
$ref: 'user.yaml#/api'
We then specify the API details of the sub-resouces in each swagger yaml file, for example pet.yaml:
openapi: '3.0.0'
api:
put:
tags:
- Pet
summary: Create Pets
operationId: createPet
parameters:
- $ref: 'common.yaml#/components/parameters/petId'
requestBody:
required: true
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/UpdatePetRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/UpdatePetResponse'
'404':
$ref: 'common.yaml#/components/schemas/BadRequest'
delete:
# .....
components:
schemas:
UpdatePetRequest:
type: object
properties:
name:
type: string
category:
$ref: '#/components/schemas/PetType'
UpdatePetResponse:
# ....
PetType:
type: string
enum:
- DOG
- CAT
You can put the common models to a common.yaml file and reference it using $ref: 'common.yaml#/'.
openapi: '3.0.0'
components:
parameters:
petId:
in: path
name: petId
required: true
schema:
type: string
responses:
BadRequest:
description: Invalid Parameters
content:
application/json
schema:
$ref: '#/components/schemas/Error'
schemas:
Error:
type: object
properties:
code:
type: string
message:
type: string
required:
- code
- message
We now have a way to seperate sub-resourcs definitions into different yaml files. Next is to add the Swagger Codegen maven plugin to generate the Java models and interfaces.
In the project root pom.xml, add the following dependencies. We exclude some dependencies from io.swagger.codegen.v3 since they are not needed and also cause some Sonarcube vulneribilites.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.27</version>
<exclusions>
<exclusion>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-generators</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
Then in the rest-api/pom.xml, we have the following:
<dependencies>
<dependency>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.27</version>
<executions>
<execution>
<id>generate-petstore-api</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/v1/petstore-api.yaml</inputSpec>
<language>spring</language>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<configOptions>
<apiPackage>se.petstore.generated.api.rest</apiPackage>
<modelPackage>se.petstore.generated.api.rest.model</modelPackage>
<dateLibrary>java8</dateLibrary>
<async>true</async>
<java8>true</java8>
<library>spring-boot</library>
<useTags>true</useTags>
<interfaceOnly>true</interfaceOnly>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
REST Client generation
To generate REST client, add the following to rest-client/pom.xml file.
<dependencies>
<dependency>
<groupId>se.petstore</groupId>
<artifactId>se.petstore.rest-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.27</version>
<executions>
<execution>
<id>generate-petstore-client</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.parent.basedir}/rest-api/v1/petstore-api.yaml</inputSpec>
<language>java</language>
<generateApis>true</generateApis>
<generateModels>false</generateModels>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<configOptions>
<invokerPackage>se.petstore.generated.api.rest.client</invokerPackage>
<apiPackage>se.petstore.generated.api.rest.client</apiPackage>
<modelPackage>se.petstore.generated.api.rest.model</modelPackage>
<dateLibrary>java11</dateLibrary>
<library>resttemplate</library>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Swagger UI
In the server application, add the SpringFox dependency:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
Then define the Docket Bean. The following configuration requires a Bearer token when makeing the API calls.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.Collections;
@Configuration
class AppConfig {
@Bean
public Docket api() {
AuthorizationScope[] authScopes = new AuthorizationScope[]{
new AuthorizationScope("global", "accessEverything")
};
SecurityReference securityReference = new SecurityReference("Bearer", authScopes);
SecurityContext securityContext = SecurityContext.builder()
.securityReferences(Collections.singletonList(securityReference))
.build();
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(new ApiKey("Bearer", "Authorization", "header")))
.securityContexts(Collections.singletonList(securityContext));
}
}