Overcoming Swagger Annotation Overload by Switching to JSON
After implementing our API using Spring Boot, we integrated Swagger using the slick SpringFox library. But all was not well.
Join the DZone community and get the full member experience.
Join For FreeWhen creating an API with accompanying Swagger documentation, two general paths can be taken:
- Build First: Implement the API --> add Swagger annotations--> generate the UI and clients from the annotations
- Design First: Design the API spec in Swagger YAML or JSON --> generate the UI, clients, and server stubs from the spec --> implement the server stubs
On my recent project, we had embarked down the "Build First" path. After implementing the API using the mighty Spring Boot, we integrated Swagger using the slick SpringFox library, as widely demonstrated in numerous blog posts.
But all was not well.
Our API had a number of endpoints that supported over a dozen query parameters, some of which were common across all endpoints (e.g. sort
, limit
, offset
, etc.). These parameters were not enumerated in the controller methods' arguments lists; instead, we accepted a single WebRequest
argument that we processed in a non-controller class. This resulted in a number of code smells:
- The Swagger annotations were separate from much of the code they were documenting. Our controller classes contained the Swagger annotations describing the parameters, but it was our
WebRequest
processor class that defined what parameters were actually handled. - Due to the commonality of our query parameters, there was a large degree of copy-paste between controller methods' annotations. The description of the
sort
parameter, for example, existed in half a dozen@ApiImplicitParam
annotations scattered across multiple controller classes. - There were more lines of annotations than actual implementation code in the controller classes. They looked like case studies from annotatiomania.com.
- The code looked aesthetically unpleasing. IDEs struggle to nicely auto-format big blocks of nested annotations containing multi-line element values.
- The Java language proved to be a clunky medium for writing documentation in. What's worse than concatenating a bunch of lengthy, markup-filled Java String literals? Concatenating a bunch of lengthy, markup-filled Java String literals within an annotation element.
I was anxious to clean up the annotation vomit, but given our implementation was essentially complete, was it too late to change direction and hop on a "Design First," specification-driven path instead? In the words of Robert Plant, a man of renowned swagger:
Yes there are two paths you can go by, but in the long run, there's still time to change the road you're on.
Finding the API Spec
The first step was obtaining the API's specification as a single, Swagger 2.0 JSON file. By inspecting the standard Spring Boot /mappings
endpoint, I found where SpringFox hosted the spec: http://localhost:8080/v2/api-docs
. This URL is also mentioned in the SpringFox documentation (I have a "Try First" rather than "Read First" personality).
Armed with the JSON, I was able to edit API documentation in YAML using Swagger Editor, which I found to be more pleasant than tweaking Strings within blocks of Java annotations.
Cleaning Up
Next, I removed all of my Swagger annotations, including @EnableSwagger2
from my main class, and theio.springfox:springfox-swagger2
dependency.
The code looked beautiful again, but without SpringFox in play, the /v2/api-docs/
endpoint no longer existed. I re-created it by copying the JSON spec to src/main/resources/swagger.json
and building a small controller class to expose it:
@RestController
public class SwaggerController {
@RequestMapping(method = GET, path = "/v2/api-docs", produces = APPLICATION_JSON_VALUE)
public Resource apiDocs() {
return new ClassPathResource("swagger.json");
}
}
Re-Enabling the Swagger UI
In addition to the core SpringFox dependency, our project relied on io.springfox:springfox-swagger-ui
to generate the Swagger UI. We were able to retain this dependency and allow the UI to continue working by recreating three endpoints that the SpringFox UI's index.html
needs for self-configuration: /configuration/ui
, /configuration/security
, and/swagger-resources
.
@RequestMapping(method = GET, path = "/configuration/ui", produces = APPLICATION_JSON_VALUE)
public Object uiConfig() {
return ImmutableList.of(ImmutableMap.of(
"docExpansion", "none",
"apisSorter", "alpha",
"defaultModelRendering", "schema",
"jsonEditor", Boolean.FALSE,
"showRequestHeaders", Boolean.TRUE));
}
@RequestMapping(method = GET, path = "/configuration/security", produces = APPLICATION_JSON_VALUE)
public Object securityConfig() {
return ImmutableList.of(ImmutableMap.of(
"apiKeyVehicle", "header",
"scopeSeparator", ",",
"apiKeyName", "api_key"));
}
@RequestMapping(method = GET, path = "/swagger-resources", produces = APPLICATION_JSON_VALUE)
public Object resources() {
return ImmutableList.of(ImmutableMap.of(
"name", "default",
"location", "/v2/api-docs", // should match the endpoint exposing Swagger JSON
"swaggerVersion", "2.0"));
}
This example controller just hardcodes the default endpoint values; a more thorough implementation would allow external configurability and perhaps use something more type-safe than collections of Strings. For even more configurability for the UI (e.g. custom look and feel, corporate header, etc.), you could embed Swagger UI directly into the application. I recommend using webjars, which is the approach SpringFox UI takes.
Wrapping It Up
Despite taking the "Build First" approach, we were able to revert to a "Design First"-like state relatively painlessly. The resulting (largely) annotation-free code was much easier on the eyes and the single definition file worked much better for documentation purposes. Perhaps in the future, I'll build an API with a design more suited for Swagger annotations or use a true design first approach. But for this particular API, changing course was the right call.
Opinions expressed by DZone contributors are their own.
Comments