Spring Boot REST with OpenAPI 3
Tags: springboot, openapi, rest, tdd
Published: 2020-05-18 00:43:05

## Project that we are building

We are going to build a todo list API, where we will have a Project that can have multiple topics. Each of the topics will have individual tasks.

A project could have one or more topics, and a topic will have one or more tasks.

In this post, we will have the project set up and API specification defined. We will then write some basic test, and integrating the generated code with our project. We will do it in the following sequences:

* Defining the API with `OpenAPI3` specification. 
* Cover a little bit about the common parts of the `OpenAPI3` 
* Generate API code in spring boot using [openapi-generator-maven-plugin](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-maven-plugin) (we will stick to maven now)
* Write tests to make sure our application works the way we want it.

If you are in a rush, and you prefer to see the ending instead, have a look at [this](https://github.com/alfonzjanfrithz/todoboard-api/commits/v0.0.1-sboot-with-openapi-3) repo instead. It has the end state of this post.

## OpenAPI 3 Specification

### Introduction

I summarize the definition from the [official website](https://swagger.io/docs/specification/about/) as follows:

> OpenAPI Specification is an API description format for REST APIs. An OpenAPI file allows you to describe your entire API, including available endpoint and operations, operation, examples, authentication method and other supporting information such as contact name, or host.

Having a standard for defining API, enables us to do a lot more automation utilizing those templates such as:
* Generating API Documentation
* Stubs/Mocks
* Communication tools for stakeholders
* Controlling changes of API version.

We can write OpenAPI3 specifications in JSON/YAML format. But in this post, we will write the definition in YAML format (I think it's easier for the eye).

To help us writing the specs, we can use swagger editor online or locally using docker image.

### Swagger Editor

Swagger Editor helps us write API Specification with some UI to ease our development.

Swagger editor available online in [editor.swagger.io](https://editor.swagger.io/) or if you want it to be in your local, you can spin up the docker container 

```
docker pull swaggerapi/swagger-editor
docker run -d -p 80:8080 swaggerapi/swagger-editor
```

We will use swagger-editor throughout the post.

### Defining the API

#### Metadata & Servers
This is useful to give information about the API and possibly the location of the API servers, so the user could have a try out on the API to the endpoints defined in the server section. The specification looks as follows:

```yaml
# OpenAPI 3 has quite a difference with OpenAPI 2, the way the linter works in the editor will be based on this.
openapi: 3.0.0  

# This section will contain metadata information about the API. It will be shown on the editor/UI when we publish this
info:
  title: To-Do Board API
  version: 1.0.0
  description: API specification to support daily to-do list operations

# The list of the servers that we can use to use the API. This will be rendered as a combo box in the editor/UI when we publish it, so the user could pick and use the endpoint they prefer.
servers:
  - url: http://dev-api.todoboard.com/v1
    description: DEV-ENV
  - url: http://api.todoboard.com/v1
    description: PROD-ENV
    
paths: {}
```

You may put those in swagger editor and you will see a bit of progress there. We move on!

#### Paths & Operation
In this section, we are putting a little bit more functionality to our API. We will create an endpoint to retrieve a project as well as topics from a project.

Let's replace `paths: {}` from the previous section to be a little more meaningful. 

```yaml
paths:
  /projects: # the API path in which the operation will be performed. When this endpoint triggered, the operation meant to be executed.
    get: # the HTTP method
      parameters:
        - in: query
          description: find project by name
          name: name
          schema:
            type: string
      operationId: searchProjects # optional unique string used to identify an operation. When the code generated, this will be the method name.
      tags:
        - Projects # group operations logically by resources or any other qualifier. In the context of spring code generation, tags is going to be the class name.
      description: retrieve all available projects in paginated way
      responses:
        200:
          description: successfully retrieved all projects
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectResponse'

  /projects/{projectId}:
    get:
      operationId: getProjects
      tags:
        - Projects
      description: retrieve a project by id
      parameters:
        - name: projectId
          in: path
          schema:
            type: integer
            format: int64
            minimum: 1
          required: true
          allowEmptyValue: false
          example: 1

      responses:
        200:
          description: the specific project successfully retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectResponse'
```
There are few things that quite important here:

* **the individual path** I think this is pretty self-explanatory, this will be the API path in which the operation will be performed. When this endpoint triggered, the operation meant to be executed.
* **method of operation** This is the HTTP method. OpenAPI 3.0 supports get, post, put, patch, delete, head, options, and trace.
* **operationId** operationId is an optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API. In the context of spring code generation, operationId will be the method name that we will need to implements.
* **Tags** used to group operations logically by resources or any other qualifier. In the context of spring code generation, tags is going to be the class name.
* **Parameters** OpenAPI 3.0 supports operation parameters passed via path, query string, headers, and cookies. We can have some specifications of the parameter, e.g whether one is required, data type, etc.
* **Schema** we can define an object that we are going to use in our request/response in a schema. It is good if we have repeated objects that we can use in multiple places

#### Components
If you notice, both of the responses in both paths above referring to a component called `Projects` which, the editor might be complaining because we haven't defined it yet.

Let's update our code as follows:
```yaml
components:
  schemas:
    ProjectResponse:
      type: object
      properties:
        id:
          type: integer
          format: int64
          description: unique id of the project for the identifier
          example: 1
        name:
          type: string
          description: the name of the project
          example: To-do Application Development
          
```

Now that we have the `OpenAPI3` definition ready, we will use that as the basis of our operation. Some parts of our code will be automatically generated based on the specification. We will talk more details in the next section.

## Spring Boot Application
Although not all the functionality exists in the API definition (we will save that for another post), we can get started to write the application. Here is what we are gonna do:

* Set up maven project with few dependencies as follows:
  * *spring-boot-starter-web* Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container
  * *spring-boot-starter-test (we can exclude the vintage engine)* Starter for testing Spring Boot applications with libraries including JUnit, Hamcrest, and Mockito
  * *springfox-swagger2* we will use the Springfox implementation of the Swagger specification.
* Set up maven build with the following plugins
  * *spring-boot-maven-plugin* The Spring Boot Maven Plugin provides Spring Boot support in Maven, allowing you to package executable jar or war archives and run an application “in-place”.
  * *openapi-generator-maven-plugin* A Maven plugin to support the OpenAPI generator project
* Write an integration test to verify the API is working as per expected using *WebMvcTest* & *MockMvc*

### Project Setup
We can use [Spring Initializer](https://start.spring.io/) to create our project template. We can add *Spring Web* dependency, so that spring initializer adds half of our needed dependencies.

Add the remaining dependency so the `pom.xml` looks like the following snippet (ignoring some parts of the pom to save the space, refer to the repository for the exact code):
```xml
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!--Required for swagger code generation-->
  <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
      <exclusion>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    
    <!--
    * This plugin is used to handle the code generation with the configuration defined in openapi-generator-config.json
    * the openapi specification will be placed in api.yaml as an imput spec
    -->
    <plugin>
      <groupId>org.openapitools</groupId>
      <artifactId>openapi-generator-maven-plugin</artifactId>
      <version>3.3.4</version>
      <executions>
        <execution>
          <goals>
            <goal>generate</goal>
          </goals>
          <configuration>
            <inputSpec>${project.basedir}/src/main/docs/api.yaml</inputSpec>
            <configurationFile>${project.basedir}/openapi-generator-config.json</configurationFile>
            <ignoreFileOverride>${project.basedir}/.openapi-generator-ignore</ignoreFileOverride>
            <generatorName>spring</generatorName>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
```

#### API Code Generation
As mentioned in the pom, there are few files we need to take care of as follows:
* *api.yml* this is where we place all of the defined API that we worked on earlier. In our case, we will put it in `/main/docs/api.yaml`.
* *openapi-generator-config.json* This file contains the configuration for the openapi-codegen to know what and how to generate the code. Refer to the official documentation for available options. In our case, we will put it in the main project directory.
* *.openapi-generator-ignore* we want to be a little bit more flexible and exclude some of the classes that we want to define ourselves, e.g Controller classes. In our case, we will put it in the main project directory.

##### _openapi-generator-config.json_
This file contains the configuration on how the openapi generator will behave. We can put this configuration under `<configuration>` in the pom, but I thought its easier to manage it externally. 

For other configuration refer to the [official repository](https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-maven-plugin/README.md)

```json
{
  "java8": "true",
  "dateLibrary": "java8",
  "useBeanValidation": "true",
  "useTags": "true",
  "generateApiTests": "false",
  "generateApiDocumentation": "false",
  "supportingFiles": "false",
  "serializableModel": "true",
  "basePackage": "com.alfonzjanfrithz.todoboard",
  "modelPackage": "com.alfonzjanfrithz.project.todoboard.model",
  "apiPackage": "com.alfonzjanfrithz.project.todoboard.api",
  "configPackage": "com.alfonzjanfrithz.project.todoboard.config"
}
```

#####_.openapi-generator-ignore_
And because we don't want to use everything from the generated class, we can actually get a few files ignored by mentioning it in the file that we put in `<ignoreFileOverride>`.

```properties
**/OpenAPI2SpringBoot.java
**/*Controller.java
```

#### Implementation
##### Write an Integration Test
Time to put some implementation in the code, I like to build a habit to write a test first before real implementation. We will put some basic implementation on the controller, write a test, and fix the test along the way.

We can now implement the *Api interfaces generated from swagger, so we don't need to implement things like `@RequestMapping` and all the parameters/headers. The plugins generated those boilerplate and hide it in the interfaces it generates. As they have the default implementation in the interface, we can leave the implementation blank and implement it later.

```java

// /main/controller/ProjectController.java
@Controller
public class ProjectController implements ProjectsApi {

}
```

```java
// /test/controller/ProjectController.java
@WebMvcTest
class ProjectControllerIT {
    @Autowired
    private MockMvc mockMvc;

    @Test
    @DisplayName("OK Response when request sent to /projects endpoint")
    public void shouldReturnOkOnProjectsEndpoint() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/projects"))
                .andExpect(status().isOk());
    }
}
```

We will get fail test now because the method that is not implemented:
```logs
java.lang.AssertionError: Status expected:<200> but was:<501>
Expected :200
Actual   :501
```

##### Controller implementation
```java
@Controller
public class ProjectController implements ProjectsApi {
    @Override
    public ResponseEntity<List<ProjectResponse>> searchProjects(@Valid String name) {
        return ResponseEntity.ok(Collections.emptyList());
    }
}
```

Our test is passed now and we can follow up with more tests and implementations.

If you are interested in further implementation that based on this project, have a look at the following post:
* [Spring Data JPA by Example](https://dev.to/alfonzjanfrithz/spring-boot-rest-with-jpa-by-example-3mfi)