While working on one of the micro services that we're adding to knowNow I ran into an issue with documentation.
In order for my teammates to quickly get up and going with the new service I needed some way to provide them
with the documentation. Preferrably something that they can use to generate code from.
Since it's 2015 that means using Swagger, a documentation format for REST services that describes the methods and
models in your service in a JSON format. Together with Swagger UI you get a documentation format that is
machine readable AND human readable. It also supports experimenting with the API, which is a big plus if you ask me.
Since I am building the micro service using Spring Boot, I had to find something that would integrate nicely with
this toolset. After a short google run around the internet I found springfox. Springfox is a very very nice library
that makes adding documentation to your spring boot application a breeze.
In this post I will show you how I did it and some of the weird things I ran into and how I solved them.
The very very quick start of Springfox
Getting started with springfox is as simple as adding a class with some beans that need to be loaded
by spring. To create this class you first need two dependencies in your project:
repositories {
jcenter()
}
dependencies {
compile("io.springfox:springfox-swagger2:2.1.1")
compile("io.springfox:springfox-swagger-ui:2.1.1")
}
For those of you that still use maven, check the docs here for information on how to add the
dependencies in Maven.
After you have added the new dependencies to your project you need to add a new class
to your application. This class will contain all the configuration needed for the API documentation.
import com.google.common.base.Predicate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseEntity;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.UiConfiguration;
import static springfox.documentation.builders.PathSelectors.*;
@Configuration
public class ApiDocumentationConfiguration {
@Bean
public Docket documentation() {
return new Docket()
.select()
.apis(RequestHandlerSelectors.any())
.paths(regex("/api/.*"))
.build()
.pathMapping("/")
.apiInfo(metadata());
}
@Bean
public UiConfiguration uiConfig() {
return UiConfiguration.DEFAULT;
}
private ApiInfo metadata() {
return new ApiInfoBuilder()
.title("My awesome API")
.description("Some description")
.version("1.0")
.contact("my-email@domain.org")
.build();
}
}
The configuration class contains one bean for the actual API documentation.
The Docklet instance created contains some bits that make the swagger documentation tick.
The first thing you want to do is to filter the API endpoints being document using the select()
operation.
This method returns an ApiSelectorBuilder
which you can use to filter the controllers
and methods being document by path using String predicates. You can also filter
the controllers and methods using RequestHandlerSelectors. You can, for example,
choose to only document controllers that have a certain annotation or name.
The above sample shows the endpoints filtered the endpoints based on the fact
that they should start with /api
. This filters out the health and metrics
endpoints which are not part of the public API in my application and should not be documented.
I suggest you use the same pattern to separate your own API endpoints from the regular
actuator endpoints exposed by Spring Boot. You don't want your customers poke around
in there let alone know about them.
After building the selector you need to map the API to the root of the website
using the pathMapping("/")
operation. This does not have any effect on the actual endpoints
served by the application. It serves as a base URL for the paths displayed in the documentation.
Finally you can append some metadata in the shape of ApiInfo, which contains things
like the contact address, title and description of the API.
When you run the application with the configuration in place you will get some
pretty decent API documentation without much effort. It doesn't look 100% right though.
Adding operation metadata
When I first ran my application with the configuration mentioned before,
I discovered that according to my docs I was returning */*
as the mimetype
for all operations on the API. That is not true, I am serving application/json, but
my documentation apparently didn't know that.
To fix the issue I had to add the property produces
to my @RequestMapping
annotation
with the value of application/json
. Once I had that in place everything was working
quite nicely.
While you are at it, fixing that, you should also add another annotation
to your operations that will make the docs even better.
With the @ApiOperation
annotation you can specify things like the default
status code and the response type being returned when the operation completes
successfully.
@ApiOperation(value = "doStuff", nickname = "doStuff", response = DoStuffResult.class)
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<?> doStuff(@RequestBody DoStuffCommand command) {
// Stuff
}
The @ApiOperation
annotation offers not only a way to give an operation a proper
name and response type when using Generic response types, you can also provide
documentation for other responses than the regular HTTP 200 OK.
@ApiOperation(value = "doStuff", nickname = "doStuff", response = DoStuffResult.class)
@Responses({
@ApiResponse(code = 404, message ="Not found", response = GenericError.class),
@ApiResponse(code = 400, message ="Invalid input", response = GenericError.class)
})
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<?> doStuff(@RequestBody DoStuffCommand command) {
// Stuff
}
Take the time to document these additional responses, because when people generate
code from your swagger spec, they will get the proper error handling for these
error responses. They will be very thankful that you helped them improve their client!
Adding model metadata
Since we're talking developer convenience here, I have one more trick to add.
By default springfox is able to document your request data and response data
when you provide the annotations I described before. You will notice however
that every attribute on every model posted to the API and returned by the API is optional.
This is probably not what you meant to tell the developer using your API.
Some attributes will be required, others will not.
There is a way to add this information to the documentation.
When you add the @ApiModelProperty
annotation to field in a model class of your
API, the springfox library will pick it up and enrich the swagger documentation
with this information.
public class Article {
@JsonProperty(required = true)
@ApiModelProperty(notes = "The title of the article", required = true)
private String title;
}
For example, when you add the required property to the @ApiModelProperty
annotation
you will let the user of your API know it is required.
You can also add other properties like notes and example to help the user better
understand what value he/she should enter in a specific attribute.
Make your API documentation visible
Now that you have properly documented your API you're probably wondering: How is my
user going to read that documentation?
When you navigate to /swagger-ui.html
you can see the documentation of your API.
Don't want this on every micro service? Just remove the swagger-ui dependency and
the UiConfiguration bean and you got rid of it.
You can point users that want to generate code based on your swagger spec to /v2/api-docs.
When you don't like what you're seeing, don't worry, this can be changed too.
Add the property springfox.documentation.swagger.v2.path
to the application.properties
file of your application and
set its value to any path you'd like the docs to be available on.
Please be aware: Changing this property will break swagger-ui. This seems to be a bug
in the springfox code. So do this at your own risk :-)