Skip to content

Instantly share code, notes, and snippets.

@bdkosher
Last active February 15, 2023 21:45
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bdkosher/902b276872819023750146d210f6edde to your computer and use it in GitHub Desktop.
Save bdkosher/902b276872819023750146d210f6edde to your computer and use it in GitHub Desktop.
My experiences with moving from Swagger Annotations to a single-file specification

Liberating an API Codebase from Swagger Annotations

When creating an API with accompanying Swagger documentation, two general paths can be taken:

  1. Build First: Implement the API --> add Swagger annotations --> generate the UI and clients from the annotations
  2. 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:

  1. The Swagger annotations were separate from much of the code they were documenting. Our controller classes contained the Swagger anntoations describing the parameters, but it was our WebRequest processor class that defined what parameters were actually handled.
  2. 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.
  3. There were more lines of annotations than acutal implementation code in the controller classes. They looked like case studies from annotatiomania.com.
  4. The code looked aesthetically unpleasing. IDEs struggle to nicely auto-format big blocks of nested, multi-line element annotations.
  5. 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 reknowned 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 JSON file (a Swagger 2.0 feature). 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 the io.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 my 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 reaches out to 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment