Swagger java annotations in action.

Igor Domrev
ITNEXT
Published in
6 min readApr 26, 2020

--

gRPC, JSON, or maybe graphQL? Each method has its advantages, a JSON RESTful API is the simplest, oldest, and most commonly used. However its simplicity comes with a price. Integrating with a REST API requires manually writing client code, which needs to be tested. Tests that tend to become slow since they need to start/stop a web server. Moreover, there are conventions and guidelines for designing RESTful APIs, and once you break one ( include a verb in the path by mistake for example ) and release to production, changing it will require to break backward compatibility, which is never fun. Because of that. Reviewing the designed API before its implemented is an important step in the process. However, writing API docs that can be reviewed manually is tedious, and remembering to update them on every change is a futile effort.

OpenApi formerly known as Swagger attempts to solve those problems and improves the development experience of api users by generating API docs ,clients, and even mock web servers out of an API description file. However, this file quickly becomes immense and complex. Editing it even using the swagger-editor becomes a pain, especially for large projects.

A sub project of the OpenApi initiative, Swagger 2.X Annotations uses reflection to generate OpenApi definitions out of code. Annotated classes, value objects, methods, and parameters are used as input to the swagger generator. The output is a swagger definition file, it can be used then to generate client/server code and API docs. Let’s look at an example.

A Service Definition

  • GET /bananas/{id}
  • POST /bananas
a simple service definition

No dependencies or frameworks. Nothing to do with HTTP headers, path parameters, or parsing/encoding the request/response body.

Notice the resemblance to a gRPC service definition.

gRPC service definition

The Google protocol buffers compiler can generate client and server code out of the above, it is also possible to use gRPC-gateway with addition to some protobuf annotations in order to generate a RESTful API bridge and even swagger definitions.

rpc definition annotated with google.api.http

The above comparison is made only to emphasize the idea of generating client/server code out of a description file. A good comparison of Swagger and gRPC can be found here.

API description as code / Swagger annotations

swagger annotations

This is the service we defined at the beginning of the post, annotated with openApi annotations. Its almost hidden by annotations, they are verbose and might confuse an unfamiliar reader. However, the alternative is manually managing a Swagger.yaml, which is worse beyond comparison.

Developers are used to an IDE. Code completion, syntax highlighting, code formatting, code folding, quick navigation, quick inspection, and keyboard shortcuts to invoke that all, greatly simplifies the process of annotating a service. Moreover, value objects and function parameters are scanned with reflection and if they change the generated API definition will change automatically. Furthermore, the swagger annotations project is well documented, and in order to understand the role of any annotation one only needs to “step into” its definition and read its javadoc comments.

Generating Swagger definitions ( swagger.json)

In order to generate the Swagger definitions we need to run a gradle task. A complete working example is available here.

In essence the Gradle configuration uses the swagger-gradle plugin and has one task that needs to be configured.

gradle configuration

by running ./gradle resolve the plugin will scan the com.banana package for classes annotated with the @Path annotation and generate src/open-api/BananaAPI.json. The above example produced a 100 line JSON file, for just two simple API endpoints.

Generating API docs

Redoc- At last, API docs you can be proud of.

Redoc is a tool that generates beautiful static api docs from swagger definitions.

Its installation and usage is super simple:

installing redoc

This will generate an index.html file that will look like this:

redoc documentation

The docs have deep linking, search, code snippets and other cool features.

Generating client/server code

  • using swagger-cli
running swagger code gen
docker run -d -p 8089:8080 -e URL=/foo/BananaApi.json -v /Users/…/personal_projects/open_api_java_annotations/open-api/:/usr/share/nginx/html/foo swaggerapi/swagger-editor

will launch a swagger editor container mounted with the swagger definitions file. By opening a browser on localhost:8089/ the editor UI will be displayed.

swagger editor

It’s possible generate the code using this UI, however clicking on the generate button will issue the following HTTP request:

generating using swagger public api

swagger public API, that accepts a JSON swagger definition file as input and returns a zip file containing the generated project.

generated client example usage (kotlin+vert.x)

java client example

Server stubs generation

One big advantage gRPC has over openAPI is the fact that open API needs to support not only almost every possible language, which it does respectfully, even Rust and Huskel. But also every web framework, and this is an impossible task, simply because frameworks change all the time. Additionally swagger does not support incremental updates, so once you update your API. By adding a route for example. Swagger can only generate the whole thing again. The only way to work around this is git merging the newly generated code with the master branch ( that contains the previous version ), all this hustle does not worth the effort in my opinion. In addition, half of the work is already done since we wrote the value objects and annotated them.

A note about project structure

All the above code is available here, its an example gradle multi-project. Following the guidelines of uncle bob in his Clean Architecture blog post. It has three subprojects

  • bananas-service (a library project that contains the service definition and swagger annotations)
  • java-client-generated (a library project that was generated by swagger)
  • web-server (the webserver, it uses the above libraries ( plugins ))

The separation to different projects is intentional, The business logic should know nothing about the webserver and the web-client. The business logic can be tested without sending or receiving HTTP requests, without starting/stopping a web server.

The web server can be tested using the generated client, it can either mock the business logic or use the real one. In my opinion, separating the different layers to different projects helps to enforce the dependencies rule ( dependencies point inwards ). But it is neither necessary or sufficient. Many other packaging technique exist to achieve the same goal.

Mock web server

Prism, an open source project can spins up a mock web server out of a swagger definitions file.

#!/bin/bash
npm install -g @stoplight/prism-cli
prism mock /Users/domrevigor/tg/svc-payments/openApi/swagger.json

This will start a web server locally that implements the api defined in the swagger json, it will use the example values provided in the swagger annotations for responses. If there’s another team that waits for the new api to be ready, they can get a mock web-server in day 1 of the development.

## example value taken from @Schema annotation
@field:Schema(description = “price in USD”, maximum = “256”, example = “5”) val price: Double,

A note regarding kotlin coroutines and swagger annotations.

Kotlin co-routines are an amazing feature of the language that allows to write simple async code, similar to javascript async/await mechanism, without the complication of futures and promises. However, when kotlin suspend functions are compiled to bytecode, they are given an additional parameter. The continuation object, which does the dirty work of hiding the Promises and futures. Swagger annotation has the @Parameter(hidden = true) annotation to handle method parameters that are not part of the API definition. However, it’s impossible to annotate a parameter existing only in bytecode. The workaround I found is, either to return a Promise from your service and keep it a regular function or return a suspend closure ( notice the = suspend { in the snippet below )

Summary

RESTfull web services are here to stay. Swagger helps others to integrate with your services by generating API docs and client code, using swagger annotations the process is even simpler, api docs are maintained with your code and does not depend on an external definitions file. It will work for any JVM language that supports annotations, like Kotlin or Scala, and does not depend on any web framework.

Resources

swagger-api/swagger-core

RESTful API Designing guidelines — The best practices | Hacker Noon

grpc-ecosystem/grpc-gateway

Redocly/redoc

swagger-api/swagger-core

swagger-api/swagger-editor

OpenAPI and gRPC Side-by-Side

Clean Coder Blog

prism — mock server

--

--