Develop REST apis with OpenApi Spec and using custom generators via templates
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
- Run the maven build with command
./mvnw clean install
. This will execute the openapi codegen plugin during thegenerate
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 -
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 beapiController.mustache
under the directoryJavaSpring
. 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 usingvendorExtensions
, 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.
- 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 ofapiController.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 annotationPreAuthorize
with roleemployee-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
更多推荐
所有评论(0)