Here we go again with a new article derived from my work notes. As you already know, I am rewriting a backend application in Kotlin and — in the process — I am improving all the horrors of legacy code I can find. In this article, we will look at one critical aspect of software development (especially for REST applications): the documentation.
Why Automatic Documentation is Important
Before this change, we documented the REST interface of this server application in Postman. Postman is a nice application, it is nice to use ad produces a really nice-looking documentation. Unfortunately, it has a big problem: it requires manual intervention.
As you know, developers are lazy scum. If you rely on developers to update your Postman collection after every change, you are a fool. Nobody really does that when you need it, and I am the worst of all.1
You want the documentation job to be as much as connected to the development job. For this reason, I opted for an OpenAPI package that generate automatically my sweet REST documentation from code.
Yes, by using these kinds of services, your code will be slightly bloated by a bunch of annotations and extra code just for documentation but, in my opinion, it is a price that I am delighted to pay!
Basic Setup
Installing the Spring OpenAPI Package
To implement this automagical generator we just need to add the following dependencies in Maven:
|
|
Or, if you are using Gradle:
|
|
Now, after you have compiled and ran the application, you can just go to localhost/swagger-ui.html
and enjoy a basic but complete REST documentation for your server application.
ℹ️ Info
Of course, change localhost
with whatever URL points to your running instance.
Springdoc, in fact, will parse your controllers and will generate the OpenAPI specification of your API. All that without a single line of code.
Improve Kotlin Support
If your application is in Kotlin, you may want to add a dependency. This will improve the introspection of Springdoc capability while parsing Kotlin code.
On Maven:
|
|
Or on Gradle:
|
|
Basic Annotations
The end result is already great: you have a nice list of endpoint and, if you click on them, you may see that Springdoc could infer URL parameters, input and output types. That’s not bad for something that required zero effort from our side.
However, we may do better. We would like to add descriptions, explanations, and improve the schemas of the different input and outputs. Let’s see some annotation that can help us.
The first one is the @Schema
annotation. This annotation is useful to describe the different part of our Data Transfer Object. Let’s assume that we have an AddressDTO
class. We may want to describe the different fields of the class.
|
|
As you can see, I use the @Schema
annotation to add a description and an example value to the class attributes. The result is exactly what you expect:
Another two essential annotations are the @Operator
and @ApiResponses
ones. These are used to document the controllers, a.k.a., the actual endpoints. As an example, let’s see a GET /users
endpoint that returns the list of all users.
The @Operator
endpoint is used like in the following example:
|
|
You can use it to provide a small summary and a small description the use case for that specific endpoint and REST verb.
The @ApiResponses
annotation, instead may be used in this way:
|
|
The annotation contains an array of @ApiResponse
annotations. Each one of them is one of the possible outcomes of a request to the endpoint (e.g., a successful operation, but also the possible error codes returned by the endpoint).
Other than the responseCode
and description
of the response, the @ApiResponse
annotation allows you to specify the content of the response’s answer. The example, I think, it is self-explanatory. The important annotation here is the @Schema
annotation. This is different from the previous one: this is used to reference an existing class as the response’s schema. In my example, PagedUsersResponse
.
This part is where 90% of your documentation effort will be. You need to document every single endpoint. However, the end result is worth the effort.
Produce a better documentation
With that, I think you have everything you need to maintain a timeless documentation masterpiece. However, if you are like me and you thrive on useless details, we can easily transform such documentation from usefull to top-quality level. How? You guessed right. With powerful Annotation Magic.
Add OpenAPI/SwaggerUI Global Configuration
You can easily configure the overall aspect of your documentation with a simple Spring Bean. Create a new Kotlin class and paste the following code.
|
|
This will add a nice title and a nice description to the main documentation page. There are more elements you can configure. Unfortunately, I have not found a good documentation for this. I’ll update the article if I find something interesting.
Enable Token Authentication
The Swagger UI used to visualize your OpenAPI documentation can also be used to test your API. Exactly. The page includes a way to send your server the example content to the endpoint and visualize the response.
To enable that, however, you need to configure the UI to allow user’s authorization. Even this time, the solution is just one Annotation Magic trick away.
|
|
In my application, I am using a Bearer Token authorization scheme. To enable that in my documentation I just need to copy the above code on top of the OpenApiConfig
bean I defined before. That’s it. Not the UI will show a nice Authorize
button.
By clicking on it, you will open a modal dialog to enter the bearer token and authorize your test calls.
Other Tips
Here they are some more interesting tips. I will add more tips as I find new obstacles in my documentation work.
Add support for custom Generic Containers
In my code I use a custom generic container for pagination defined as:
|
|
So, for example, if I return a page of User
instances, I will define my response body as PageDto<UserDto>
. Unfortunately, in the @ApiResponse
annotation I cannot reference such generic class. If I try to do something like this:
|
|
The application will not compile because we cannot get a ::class
reference of a generic definition.
The good news is that there is an easy workaround. In your controller you need to define an internal private class that extends the generic class you need. Like this one:
|
|
And then use this class in your @Schema
annotation.
|
|
Problem solved. It is annoying to have to define a class just for your documentation engine. However, it is a sacrifice I am happy to do.
Conclusion
That’s how I am documenting my Spring application. What is your opinion? Do you like to make your code a bit dirty to have an auto-generated documentation or do you are willing to die on the hill of “code is just for logic”? Let me know! See you next time.
That’s funny given that I find writing documentation almost fun and I am constantly bitching about how “we really should write documentation!” ↩︎