Documenting your REST APIs
Whenever you deliver some API that is to be consumed by another party, you will get the inevitable question of providing documentation. Probably every developer’s least favorite task.
In Java there is javadoc, but that doesn’t cut it if you are delivering a Web Service API. In that realm we already know WSDL for SOAP based Web Services. Then again, every developer seems to prefer REST based Web Services these days and those are not WSDL based… So what then? That is a question with multiple answers. In the last few years there have been three different open-source projects that have tried to give an answer to this: swagger, RAML and API BluePrint. Of those Swagger has been around the longest and arguably gained the largest following.
Based on the completely subjective criteria ‘it needs to support Java’, ‘what about Spring MVC?’ and ‘can you deliver it to the customer by Monday?’ I decided to take a stab at documenting an existing API using Swagger. It is written in something that can run on a JVM, has Spring MVC support (via third party libraries) and seemed to be the easiest to set up based on their examples and various other online resources.
Swagger 2.0?
First things first. Like everyone else I took a look at the documentation (you do that too right? right???) and found that Swagger 2.0 had been out for some time and seemed to address various complaints about version 1 in comparison with the other two projects. Of the libraries that offered Spring integration the one called springfox seemed to be both still active and have Swagger 2.0 support.
Next I started to embark on a tour of the examples and tried to get everything to work. Most of it was figuring out the right maven dependencies (for some reason JCenter seems to be the place for those these days),
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.0.0-EARLYACCESS</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-spring-web</artifactId> <version>2.0.0-EARLYACCESS</version> </dependency>
spring configuration in both XML
<context:annotation-config/> <bean id="swagger2Config" class="springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration"/> <bean id="springConfig" class="com.example.SpringConfig"/><context:component-scan base-package="com.example.controllers"/>
and Java,
@Configuration @EnableSwagger2 public class SpringConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(WEB_JAR_RESOURCE_PATTERNS) .addResourceLocations(WEB_JAR_RESOURCE_LOCATION).setCachePeriod(0); } @Bean public InternalResourceViewResolver getInternalResourceViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(WEB_JAR_VIEW_RESOLVER_PREFIX); resolver.setSuffix(WEB_JAR_VIEW_RESOLVER_SUFFIX); return resolver; } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } }
and why oh why my xml-based spring configuration was clashing with the Java-based configuration of the spring fox libraries…. In the end I found that the Swagger2DocumentationConfiguration bean brings in a class with the @EnableWebMvc annotation and that one doesn’t work very well when you have xml configuration using a <mvc:annotation-driven> section. Moving the latter to Java config got me to a working /v2/api-docs end point.
Then came the task to get it all to work nicely. For that I wanted to get the Swagger UI to work as well. According to the examples I should be able to get it to work with adding yet another dependency
<dependency> <groupId>org.ajar</groupId> <artifactId>swagger-spring-mvc-ui</artifactId> <version>0.4</version> </dependency>
At this point I hit the fact that the migration to Swagger 2.0 wasn’t complete yet for all of the components in the ecosystem. Whether it was in Swagger UI or in springfox I couldn’t figure out, but in the end I kept getting issues where the JavaScript library kept complaining about getting an outdated version of the api-docs format after which it would crash on an undefined property.
For those now wondering if there is still a happy end to this story, stick with me a little longer.
Swagger 1.2
After a while trying several combinations of libraries and configuration, I gave in to the ‘can you deliver it to the customer by Monday’ requirement and took the pragmatic approach and used the Swagger 1.2 versions of all the components. This meant I had to change the maven dependencies for the io.springfox groupdId to the following:
<dependency> <groupId>com.mangofactory</groupId> <artifactId>swagger-springmvc</artifactId> <version>1.0.2</version> </dependency>
and make some minor adjustments to my earlier configuration:
@Configuration @EnableSwagger public class SwaggerSpringConfig extends WebMvcConfigurerAdapter { @Autowired private SpringSwaggerConfig swaggerConfig; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(WEB_JAR_RESOURCE_PATTERNS) .addResourceLocations(WEB_JAR_RESOURCE_LOCATION).setCachePeriod(0); } @Bean public InternalResourceViewResolver getInternalResourceViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(WEB_JAR_VIEW_RESOLVER_PREFIX); resolver.setSuffix(WEB_JAR_VIEW_RESOLVER_SUFFIX); return resolver; } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Bean public SwaggerSpringMvcPlugin swaggerSpringMvcPlugin() { return new SwaggerSpringMvcPlugin(swaggerConfig) .directModelSubstitute(DateTime.class, String.class); } }
and all of a sudden I had a working api-docs endpoint as well as a, more or less, working Swagger UI page at the url http://localhost:8080/sdoc.jsp. And without any changes to my actual code.
Customising the api-docs
The API documentation so far is using a number of place holders for the title, description, version and some other properties. To fix that all we have to do is make some minor changes to the spring configuration:
@Bean public SwaggerSpringMvcPlugin swaggerSpringMvcPlugin() { ApiInfo apiInfo = new ApiInfoBuilder().title("My custom title") .description("The custom description for my REST API") .build(); return new SwaggerSpringMvcPlugin(swaggerConfig) .directModelSubstitute(DateTime.class, String.class) .apiInfo(apiInfo) .apiVersion("2.0"); }
Further adjustments to the methods and their payload, both request and response, are a bit more invasive and require some Swagger annotations (all conveniently prefixed with Api for some reason) to be placed on the class, method or property that needs a bit of moulding.
What’s next?
We now have a documented REST API, which is better than nothing I would say, and at a minimum of effort at that. It will also automatically update as it is using the actual implementation to generate the documentation, a nice bonus! So lets use it and see where we find something we could improve upon.
Beyond that I’m also interested to see what happens if we take the other way round and start with the API specification, generate the interface classes from that and then implement it.