Specification First: Make Life Easier with OpenAPI and Spring

Practical guide how to build a robust CRUD web-service with OpenAPI on Java / Spring Boot stack

NomaD
Devexperts
6 min readFeb 27, 2020

--

Building an application with REST API is not rocket science. However, involving modern approaches, such as cloud architecture, brings in a lot of complexities. We had been looking for a solution which could allow us to create an API in an efficient and straight-forward way. If accomplished, all our specifications would be stored in a single place and could be used for automatic code generation. One of the most popular standards to achieve this goal is OpenAPI, also known as Swagger, the official documentation describes it the best:

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. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.

Basically, there are two possible approaches to API development with OAS. The one known as Code-First is more commonly used in the Java world. The programmer builds an application based on business requirements and the documentation is generated from the source code. In this case OAS can be compared to an architectural illustration of the Empire State Building drawn a long time after it was constructed. In the Design-First or Specification-First approach OAS is more like a detailed drawing of the skyscraper including elevators, water pipes, and electrical wiring which the architect is going to construct. Unlike the architectural design, in the Java world the sketch changes can be made at any stage, even when the service is already functioning.

In the following chapters, we will see how we can benefit from a Specification-First design and construct a Spring Boot project from scratch with the most recent OpenAPI version 3.0.2.

Table of Contents

Step 1: Creating a gradle project

The first thing we have to do is create an empty project, e.g. with Spring Initializr. Don’t forget to specify a group name (com.devexperts) and an artifact name (accounts). Make sure to include the following dependencies:

  • Spring Boot 2.2.2 — the most recent stable framework version;
  • Spring Boot DevTools — tools for faster development;
  • Lombok — plugin to reduce boilerplate;
  • Spring Web — REST API;
  • Spring Data JPA — persistence layer;
  • H2 Database — in-memory database for faster development;
  • Spring Boot Actuator — monitoring tools.

At this point our build.gradle looks like this:

Step 2: Configuring OpenAPI Generator

OpenAPI Generator is a powerful code generation tool which supports various programming languages. Specifically, it provides a gradle plugin which can be easily incorporated into our project. Moreover, we might want to have an IDE plugin to make the code generation happen automatically on project refresh.

Some other dependencies are needed for automatic generation of swagger documentation and for the support of nullable fields.

Generator plugin can be customized in different ways. All available properties are described in its documentation. Let’s start with a simple configuration:

We should also define compileJava section to make sure the code generation happens before project is being compiled and sourceSets to be able to see generated classes from the IDE editor. In the springBoot section we are supposed to define our application’s main class.

Step 3: Writing a specification

Now it’s time to kick off the core part of the project. In our example, we are going to make CRUD API for an account service intended to operate customer accounts data: customer name, email and account balance. Let’s create a file src\main\resources\api\accounts-spec.yaml and begin to fill it in step by step. The first line of any specification is a version definition followed by an info section. Please, be aware that YAML format uses a fixed indentation scheme!

The main part of the API is paths section. A typical CRUD consists of 5 operations:

  • find all entities;
  • create a new entity;
  • get entity by id;
  • update entity by id;
  • delete entity by id.

The first one is optional. Moreover, in most cases we should not allow users of the API to retrieve all available entities at the same time as it could affect performance (imagine we have millions of accounts in the database). To address this issue, we are going to introduce page and limit parameters in order to enable pagination and to limit the subset of requested objects.

Let’s fill the contents of each operation. Note that $ref keyword references the components which will be defined later in the document.

A very convenient feature of OpenAPI standard is Components Object — a set of reusable objects such as schemas, parameters, responses. Schema — is a definition of an object used across the specification as input or output data. Objects can include properties and be combined with each other by allOf, oneOf, anyOf keywords. As far as we are having PATCH method for update operations, the user of the API is allowed to specify a subset of fields he wants to update (as opposed to PUT, where the original entity is replaced by the new one), so the fields are marked as nullable.

Responses and parameters are defined once and used across the specification as well. It is also common to define such components as requestBodies, headers, securitySchemas, but we do not include them for now to make CRUD API as simple as possible.

Step 4: Running the application and viewing docs

The entry point of every Spring Boot project is an application class. Let’s define it as src\main\java\com\devexperts\accounts\AccountsApp.java.

Since now, we are able to build and run the service, typing localhost:8080 into the browser will redirect us to the generated swagger page.

Step 5: Building persistence layer

Persistence layer is being built on the top of Spring Data JPA and includes an entity class (Account) and a repository interface (AccountRepository). We are also using lombok to generate such boilerplates as getters, setters, constructors.

Extending our interface from JpaRepository will automatically make it managed by Spring Data. No implementation is required unless we need a custom one.

Step 6: Building service layer

OpenAPI Generator as configured above creates API model classes with Json suffix. Such classes are intended to be used inside the API layer and therefore we have to implement a mapper from API model classes to domain classes and vice versa:

  • CreateAccountJson => Account
  • Account => AccountJson
  • UpdateAccountJson => AccountPatch

We have already built an Account class, now let’s make an AccountPatch.

In the real-world mappers are usually implemented with such frameworks as ModelMapper or MapStruct. Below is an example of a simple Java class, where AccountMapper interface just exposes its methods and @Component annotation, meanwhile, makes the concrete class managed by Spring.

The service for account objects is implemented in a similar way. @Service annotation makes it managed by Spring. You can also notice the methods are marked as @Transactional for the support of automatic transaction management.

AccountService also uses a custom runtime exception to notify that something went wrong, e.g. the user has sent a request to update an entity which doesn’t exist.

Step 7: Building API layer

As far as we enabled the delegate pattern in OpenAPI Generator configuration, both AccountsApiController and AccountsApiDelegate are present in generated sources. We are going to implement the latter while leaving the controller as is.

The last thing is AccountsExceptionHandler which is responsible for mapping service layer exceptions to API errors defined in the specification.

Well done, the application can now be compiled and run. We are able to try the API in action either from the build-in swagger page or with tools like Postman.

The source code is available at github.

Conclusion

This is how we have been implementing APIs in our projects using a Specification-First design. Remember that we are not limited to server code generation, we can use the same specification for generation of client-side JS code and Feign clients for microservice-to-microservice interaction. Tools that support OpenAPI can help you to focus on business level, make specification independent of implementation, generate some boilerplate for implementation, and reuse specifications across multiple services or clients.

--

--

NomaD
Devexperts

Just a Java backend developer interested in building scalable and robust applications using modern technologies and patterns.