This example project demonstrates how to connect a browser-based frontend application, a backend-for-frontend application and a microservice application via HTTP interfaces using contract-first OpenAPI specifications and automatic code generation.
-
Java Development Kit 10 (or higher)
-
Node.js 8.9.x and NPM 5.6.x (or higher)
-
Free local ports: 10080, 10081, 10082
-
Clone the Git repository.
-
Open a terminal in the subdirectory
microservice-application
and runmvnw spring-boot:run
. This will start the microservice on local port 10082. -
Open a terminal in the subdirectory
bff-application
and runmvnw spring-boot:run
. This will start the BFF on local port 10081. -
Open a terminal in the subdirectory
frontend-application
, then first runnpm install
and thennpm start
. This will start a HTTP server for the frontend on local port 10080. -
Open your browser and navigate to http://localhost:10080/
The Spring Boot application in the subdirectory microservice-application
represents a backend service which provides a rather general JSON-API for a specific business domain - in this example an API to retrieve messages.
-
The provided JSON-API is defined using the OpenAPI specification file
src/main/resources/static/api/v1/microservice-api.json
. -
During the Maven build process the
openapi-generator-maven-plugin
is used to generate a Spring-annotated Java interface and Java model classes for the response data structure directly from the specification file. -
The generated Java interface is implemented by the
MessagesController
class. This controller implementation is additionally annotated with@RequestMapping("/api/v1")
to serve the API underneath that base path. -
There is an automated test for the controller implementation in the
MessagesControllerTests
class to verify the runtime API compliance.
-
The OpenAPI specification file is intentionally located inside the
static
directory to have Spring Boot serve it automatically at http://localhost:10082/api/v1/microservice-api.json -
Only a Java interface with Spring
@RequestMapping
annotations is generated from the specification file to separate the interface contract from the server implementation. The goal is to generate the Java interface during each Maven build directly from the specification file but still have a persistent implementation of that interface which is written by developers manually. As soon as operations or parameters are modified in the specification file it will force the server implementation to adapt to these changes. -
Java model classes are generated from the specification file to ensure compile-time check of these classes used to bind API requests and responses. When some request or response data structure is changed in the specification file it will force the server implementation to adapt to these changes.
-
The Spring controller implementation is mapped to the base path
/api/v1
rather than setting the context path of the whole application to this path. This allows the application to still serve resources outside of this versioned API path and allows to implement even two versions of the API within the same application version.
The Spring Boot application in the subdirectory bff-application
represents a frontend-oriented API gateway - also called a backend-for-frontend (BFF) - which provides a simple API in JSON format to allow the frontend application to retrieve a view on the data which is really required by the frontend - in this example only the plain text of a specific message (without any technical data like ID). The message data is loaded via request to the microservice application and transformed to the simpler view model.
-
The provided API is defined using the OpenAPI specification file
src/main/resources/static/api/v1/bff-api.json
. -
During the Maven build process the
openapi-generator-maven-plugin
is used to generate a Spring-annotated Java interface and Java model classes for the response data structure directly from the BFF specification file. -
The generated Java interface for the BFF API is implemented by the
MessagesController
class. This controller implementation is additionally annotated with@RequestMapping("/api/v1")
to serve the API underneath that base path. -
There is an automated test for the controller implementation in the
MessagesControllerTests
class to verify the runtime API compliance. -
The consumed JSON-API of the microservice is defined by the OpenAPI specification file
src/main/openapi/microservice-api.json
. It is an exact copy of the file from the microservice application. -
During the Maven build process the
openapi-generator-maven-plugin
is used to generate a Java interface from that microservice specification file. -
The generation of the Java interface for the microservice API is customized by the Mustache template located at
src/main/openapi/templates/api.mustache
. It is a slightly modified version of the original template. -
The generated Java interface for the microservice API is extended by the
MessagesClient
interface to implement an OpenFeign client named "messages" which is used by the BFF to access the API of the microservice. -
There is an automated test for the OpenFeign client in the
MessagesClientTests
class to verify the runtime API compliance. The test is supported by an embedded WireMock server which is configured by the files located insrc/test/resources/mocks/messages
.
-
The OpenAPI specification file is intentionally located inside the
static
directory to have Spring Boot serve it automatically at http://localhost:10081/api/v1/bff-api.json -
For providing the BFF API, only a Java interface with Spring
@RequestMapping
annotations is generated from the BFF specification file to separate the interface contract from the server implementation. The goal is to generate the Java interface during each Maven build directly from the specification file but still have a persistent implementation of that interface which is written by developers manually. As soon as operations or parameters are changed in the BFF specification file it will force the server implementation to adapt to these changes. -
For consuming the microservice JSON-API, a Java interface with Spring
@RequestMapping
annotations is generated from the microservice specification file to support implementation of an OpenFeign client which is always compliant with the specification. When some operation or parameter of the microservice API is modified the BFF will be forced to adapt to these changes. -
The Java interface generation for the OpenFeign client is customized for two reasons:
-
When using Java 8 language level with the OpenAPI generator there is no way to turn off the generation of
default
methods in the Java interface. But this is problematic with Feign clients because such methods do not really execute requests in the end. Therefore, the generation of thedefault
method stub must be removed. -
Methods generated for API operations only have a
@RequestMapping
for their pure operation path, not including any base path of the API. This is problematic with Feign clients in combination with Ribbon and service discovery because it is impossible to configure the base path/api/v1
for a Feign client but still preserving the flexibility of Ribbon load-balancing. Therefore, the request mapping in the Java interface must include the base path of the API given by the variable{{{contextPath}}}
.
-
-
Java model classes are generated from the BFF and microservice specification file to ensure compile-time check of these classes used to bind API requests and responses. When some request or response data structure is changed in the specification files it will force the server implementation to adapt to these changes.
-
The Spring controller implementation is mapped to the base path
/api/v1
rather than setting the context path of the whole application to this path. This allows the application to still serve resources outside of this versioned API path and allows to implement even two versions of the API within the same application version.
The frontend application in the subdirectory frontend-application
represents a browser-based JavaScript application which makes use of the API exposed by the BFF application to display that server-provided data on the web page. The application code is written in TypeScript and packaged into a JavaScript bundle using Webpack.
-
The consumed API of the BFF is defined by the OpenAPI specification file
src/api/bff-api.json
. It is an exact copy of the file from the BFF application. -
The
openapi-generator-maven-plugin
is used during the build process to generate afetch
-based HTTP client module from the specification file. This little Maven build is embedded as part of the NPM build using a Maven wrapper. -
The main module
src/index.ts
imports the generated HTTP client module located atsrc/api/bff-api-client
and uses it to retrieve the message text from the BFF.
-
Automatically generating the HTTP client in TypeScript ensures that the frontend always uses a client implementation which is compliant with the OpenAPI specification file. As soon as the operations, parameters or message data structures are modified the frontend is forced to adapt to these changes.
-
TypeScript is a crucial tool decision to make sure that correct interaction of the frontend business logic with the generated HTTP client is checked already at compile time.
-
The embedded Maven build is required to stick with the official OpenAPI tooling for code generation. All pure Node.js based generators were not really ready for OpenAPI 3.0 at the time of this writing.
The subdirectory spec-server
contains a small ExpressJS application which can be used to serve the Swagger UI for multiple OpenAPI specification files. This can be helpful for development teams to have an overview of all the available APIs and gives them a nice view of the specifications.
-
Open a terminal in the subdirectory
spec-server
, then first runnpm install
and thennpm start
. This will start the ExpressJS server on local port 3000. -
Open your browser and navigate to http://localhost:3000/
Modern web APIs can be easily defined using the OpenAPI specification. These specification files can be designed before any real implementation of applications begins (contract first). As soon as the API has been defined the specification can be shared with several development teams to stark working on client and server implementations in parallel.
The OpenAPI specification files can also be used to generate source code which helps to implement API compliant servers and clients. There is the possibility not only to generate an initial project from the specification but also to have API-related source code generated again during each build process. This helps to keep the application code in sync with the API specification.
Additionally, having the OpenAPI specification files for both the provided and consumed APIs inside each application makes it easier to find out which versions of connected applications can play together.