Make documentation great again (Bonus)
This article is part of a series called "Make documentation great again"
After the general presentation of Spring REST Docs, I propose to go further by exploring small improvements that will make all the difference!
We will continue to base ourselves on my demonstration project available on GitHub: demo-spring-rest-docs.
Expose the documentation "automagically"
As we have seen, we are now able to generate an HTML page containing the documentation of our API. But that’s not the best way to make it available to API consumers, we can do better than that!
And what better place than the Spring Boot application itself?
For that, some configuration is required, thanks to the Maven plugin maven-resources-plugin
.
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/static/docs/</outputDirectory>
<resources>
<resource>
<directory>${project.build.directory}/generated-docs</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
This code copies the contents of the target/generated-docs
directory to a target/static-docs/docs
directory.
In other words, the generated HTML page is placed in a somewhat special directory, since static
is automatically served by Spring.
If you want to learn more about this feature, Baeldung has a dedicated page: Baeldung.
With this configuration, if you build the application (mvn package
), you will get a JAR file that you can launch (java -jar demo-spring-rest-docs-0.0.1-SNAPSHOT.jar
).
The API will be exposed, but the documentation will also be accessible at http://localhost:8080/docs/index.html.
So as soon as the application is deployed somewhere, its documentation will be present!
Hide oversize elements by default
Sometimes we need to document endpoints that return relatively long responses.
This can make the documentation page quite heavy.
Fortunately, AsciiDoc has a tag to solve this problem: [%collapsible]
.
Everything inside a tagged block is hidden by default, and the reader needs to click on it to expand it:
Large Element (click here to expand)
[%collapsible]
====
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Phasellus eget mollis neque.
Etiam tempor lacinia lorem eget auctor.
Quisque accumsan leo a tincidunt hendrerit.
Nam eros ante, scelerisque eu tempus et, vestibulum luctus turpis.
Donec id nisi risus.
Nullam eu purus vulputate velit pharetra hendrerit.
Donec varius, velit vitae aliquam interdum, dui sapien faucibus ipsum, et sollicitudin ligula magna quis velit.
Donec luctus sed nisi ac blandit.
Phasellus sodales mattis pharetra.
Duis dignissim tellus nibh, quis imperdiet turpis pharetra et.
Phasellus purus odio, pulvinar vel urna vel, consequat vulputate metus.
Nunc elementum ornare eleifend.
Pellentesque non dapibus ipsum.
Nunc malesuada varius elit, auctor tristique nisl pellentesque sed.
Vestibulum justo mauris, molestie ut tincidunt a, condimentum ac turpis.
Aliquam eu interdum orci.
====
This is how we find, for example, in the documentation of our demo application:
.Response
[%collapsible]
====
include::{snippets}/getAllCompanies/http-response.adoc[]
====
Simplify request/response descriptions
To describe the request related to an endpoint, there’s nothing better than the http-request
snippet.
It contains the URL, the HTTP verb, headers, and optionally the body.
However, headers are often numerous, especially those related to security. The same applies to the response, with many headers added by the framework and its tooling.
If these headers have business meaning, that’s great. But if they only create "noise," it’s better to remove them from the description (both in the request and the response) to make it clearer.
I propose a small utility (ControllerTestUtils
) to avoid repeating the same header removal in each test:
public class ControllerTestUtils {
static OperationRequestPreprocessor preprocessRequest() {
return Preprocessors.preprocessRequest(removeHeaders("Content-Length", "X-CSRF-TOKEN"), prettyPrint());
}
static OperationResponsePreprocessor preprocessResponse() {
return Preprocessors.preprocessResponse(removeHeaders("Content-Length", "Pragma", "X-XSS-Protection", "Expires", "X-Frame-Options", "X-Content-Type-Options", "Cache-Control"), prettyPrint());
}
}
Feel free to remove specific headers according to your preferences or needs.
For example, let’s consider the Content-Type
header in the request to delete a Company
.
You just need to add it to the list of headers to remove (see the code above).
DELETE /companies/ID_1 HTTP/1.1
Content-Type: application/json;charset=UTF-8
Host: localhost:8080
{
"id" : "ID_1",
"name" : "CoolCorp",
"location" : "Paris",
"creationDate" : "2021-11-06T11:03:53.066+00:00"
}
DELETE /companies/ID_1 HTTP/1.1
Host: localhost:8080
{
"id" : "ID_1",
"name" : "CoolCorp",
"location" : "Paris",
"creationDate" : "2021-11-06T11:01:34.532+00:00"
}
Generate an OpenAPI file
"Hey, this is all great, but in my team, we’re used to using Swagger. It’s a more comprehensive tool, we can even make requests directly from the documentation page!"
That’s an interesting remark!
Moving away from Swagger means going from an interactive page to a completely static HTML page.
So, would we lose out?
Perhaps… but let’s not be hasty!
Let’s get straight to the point: it’s entirely possible to generate a file describing the API in a "Swagger-compatible" format, i.e., the OpenAPI format. However, it’s not available by default. We need to add a small dependency that extends the capabilities of Spring REST Docs:
<dependency>
<groupId>com.epages</groupId>
<artifactId>restdocs-api-spec-mockmvc</artifactId>
<version>0.14.1</version>
<scope>test</scope>
</dependency>
And when documenting an endpoint, we now need to import the document()
method from this dependency:
import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;
When running the tests again, a new "snippet" is generated: resource.json
.
It’s a snippet in the OpenAPI format. Now, all we have to do is gather these snippets into a single file, just like we already do for the AsciiDoc version. However, this time we don’t need a "root file"; we will use a plugin.
But… there’s a problem ahead!
The added dependency only provides a Gradle plugin, and our project uses Maven!
No need to panic, though.
The community is vast and considerate, and there is a plugin for generating the final documentation: restdocs-spec-maven-plugin
.
By adding it to the "build" section of the pom.xml
file, we can generate an OpenAPI file in addition to the previous HTML file:
<plugin>
<groupId>io.github.berkleytechnologyservices</groupId>
<artifactId>restdocs-spec-maven-plugin</artifactId>
<version>0.21</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<name>Demo Spring REST Docs</name>
<version>${project.version}</version>
<host>localhost:8080</host>
<outputDirectory>${project.build.directory}/generated-docs</outputDirectory>
<filename>openapi</filename>
<specification>OPENAPI_V3</specification>
<description>API description for Demo Spring REST Docs service</description>
</configuration>
</execution>
</executions>
</plugin>
Similar to the HTML file, since this openapi.yml
file is located in an exposed directory, it will be accessible once the application is launched.
From that point on, you are free to provide it as the entry point to an instance of Swagger UI.
To do that, you can quickly test the quality of the generated file by starting an instance of Swagger UI:
docker run -p 80:8080 swaggerapi/swagger-ui
Once the container is up and running, simply go to http://localhost (assuming Swagger UI is running on port 80 based on our above command) and provide the address of the openapi.yml
file in the search bar at the top of the page.
The documentation will appear… with all the usual Swagger functionality.
So, is the developer with their own habits satisfied?
So, are you starting to be convinced by Spring REST Docs?
In any case, I am convinced, and I will continue to deploy it on the projects I have the pleasure of working on!
Useful links: