Hello Apiary, Java Annotations and Swagger YAML
As I'm finishing up the preparations for the class Oliver Szymanski and I are teaching at the JavaLand Schulungstag, I wanted to take some time to blog about some aspects of a new workflow for RESTful API design. If you are going to JavaLand, do check out my previous post where I list my session picks. This workflow involves several disparate technologies, which is now commonplace in today's mega-heterogeneous, optimized for evolution, development environment. This blog post will start with a simple Java interface, decorate it with JAX-RS annotations, further decorate it with OpenAPI Initiative (aka Swagger) API documentation annotations, upload it to Apiary, and close by handing off to further tutorials.
Starting Simple
Keeping it simple is harder now than ever. In my opinion, this is due to the presence of powerful market forces that need to monetize the act of getting things done. These forces insidiously inject themselves wherever possible into the process. So don't get discouraged if things get complex very fast. Just realize it's a constant process to preserve simplicity, the first step of which is starting simple. We're going to design a simple Hello World REST service, starting with this Java interface.
- import javax.ws.rs.core.Response;
- public interface HelloApiary {
- }
The idea is that the actual JAX-RS/Jersey code will implement this interface, but as this post focuses on the API design, we will not examine that aspect.
The next step is to apply the standard JAX-RS annotations:
- import javax.ws.rs.GET;
- import javax.ws.rs.Path;
- import javax.ws.rs.Produces;
- import javax.ws.rs.QueryParam;
- import javax.ws.rs.core.MediaType;
- import javax.ws.rs.core.Response;
- @Path("20170204")
- @Produces(MediaType.APPLICATION_JSON)
- public interface HelloApiary {
- @GET
- Response sayHello(
- );
- }
The act of sprinkling these annotations on the interface says a lot.
Line 9 declares that this REST endpoint will be listening at the
path 20170204
. This is kind of like a servlet path
mapping. We use an 8 digit date as the path to give us a simple,
unambiguous version scheme. This is important to enable API evolution
while preserving compatibility with existing clients of our API.
Line 10 declares that the API will be returning JSON.
Line 13 declares that the sayHello
method should be
invoked when an HTTP GET request comes to the
path 20170204
. Line 15 says the value of
the name
argument will be provided by the URL query
parameter name
. Note the duplication here.
Unfortunately this kind of duplication seems to be an accepted cost in
this style of API design.
Now we come to the matter of how to document our RESTful API, which
brings us back to monetization: several competing solutions exist,
each with their own agenda for success. The best way to do this is
still an open question, and depends on finding a good fit between what
you have (in terms of current developer skillset) and what you want
(in terms of finished documentation). I have chosen to strike a
compromise that gives me flexibility and options: use
the OpenAPI Initiative Java
Annotations to produce a swagger.yaml
file. OAI
(formerly Swagger) is not the only choice.
These two articles
describe some of the other options and the tradeoffs in each. These
tradeoffs are just the latest iteration in the long and rich history
of API
languages. In my opinion, this iteration seems to have more steam
and sticking power than any previous iteration. I don't think we can
say, “This time we finally got it right,” but it may be
the case that the winner in this round will have so much adoption that
any other options simply can't compete.
Hello Betamax.
Getting More Complex
So, sticking with the OAI annotations, we now have:
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import io.swagger.annotations.ApiParam;
- import io.swagger.annotations.ApiResponse;
- import io.swagger.annotations.ApiResponses;
- import javax.ws.rs.GET;
- import javax.ws.rs.Path;
- import javax.ws.rs.Produces;
- import javax.ws.rs.QueryParam;
- import javax.ws.rs.core.MediaType;
- import javax.ws.rs.core.Response;
- @Api("HelloApiary")
- @Path("20170204")
- @Produces(MediaType.APPLICATION_JSON)
- public interface HelloApiary {
- @ApiOperation(value = "Simple echo of query string value",
- notes = "If no query string is present return 500.")
- @ApiResponses(value = {
- @ApiResponse(code = 200,
- message = "Echo",
- @ApiResponse(code = 500,
- message = "Missing parameter",
- })
- @GET
- Response sayHello(
- @ApiParam(value = "Your name")
- );
- }
This is starting to get complex, and it's still just hello world. Note that there is another implicit choice here: No javadoc. To make this mess of annotations readable to developers, we need further tooling. This is one area where the choice of OAI does offer some real advantages over Javadoc. All of the tooling for generating developer documentation of RESTful APIs seems to provide at least some level of built-in interaction for testing and experimentation. This blog post will use maven and the kongchen Swagger Maven Plugin. Here is the complete POM.
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <artifactId>20170204-apiary</artifactId>
- <groupId>com.ridingthecrest</groupId>
- <version>1.0</version>
- <packaging>jar</packaging>
- <name>Apiary example</name>
- <properties>
- <maven.jar.plugin.version>2.6</maven.jar.plugin.version>
- <maven.compiler.plugin.version>3.3</maven.compiler.plugin.version>
- <swagger.version>1.5.10</swagger.version>
- <javaee.api.version>7.0</javaee.api.version>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <swagger-maven-plugin-version>3.1.1</swagger-maven-plugin-version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>io.swagger</groupId>
- <artifactId>swagger-core</artifactId>
- <version>${swagger.version}</version>
- </dependency>
- <dependency>
- <groupId>javax</groupId>
- <artifactId>javaee-api</artifactId>
- <version>${javaee.api.version}</version>
- <scope>provided</scope>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>com.github.kongchen</groupId>
- <artifactId>swagger-maven-plugin</artifactId>
- <version>${swagger-maven-plugin-version}</version>
- <configuration>
- <apiSources>
- <apiSource>
- <locations>com.ridingthecrest.apiary</locations>
- <info>
- <title>Hello Apiary</title>
- <version>v20170204</version>
- <description>Hello</description>
- </info>
- <outputPath>${project.build.directory}/generated/document.html</outputPath>
- <outputFormats>yaml</outputFormats>
- <swaggerDirectory>${project.build.directory}/generated/swagger-ui</swaggerDirectory>
- <swaggerApiReader>com.wordnik.swagger.jaxrs.reader.DefaultJaxrsApiReader</swaggerApiReader>
- </apiSource>
- </apiSources>
- </configuration>
- <executions>
- <execution>
- <phase>compile</phase>
- <goals>
- <goal>generate</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>${maven.compiler.plugin.version}</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <version>${maven.jar.plugin.version}</version>
- <executions>
- <execution>
- <goals>
- <goal>test-jar</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- <profiles>
- </profiles>
- </project>
We follow the maven best practice of collecting our versions in
the properties
section, on lines 11 - 18. At compile
time we only depend on two things: the OAI annotations jar and the
JAX-RS API jar, on lines 20 - 31. Most of the action happens in the
plugins section. The maven-compiler-plugin
and maven-jar-plugin
declarations, on lines 65 - 85, are
just stock. The swagger-maven-plugin
declaration, on
lines 36 - 64 is the important part. This example shows the minimum
configuration you need in practice. The plugin documentation has a
full and rich set of configuration.
Line 43 is the java package for which to generate the API. This is a
comma separate list of java packages. Line 45 is the API version.
Here again, is some unfortunate duplication with the Java code. You
could use maven string filtering to template a value into the Java
code. This is an instance of how perserving simplicity (avoiding
duplicates) takes additional complexity. Sigh.
The outputPath
on line 49 shows where to place all the
output (not just the document.html). Finally, and most importantly
for our purposes, the outputFormats
element. I had to
choose yaml here because even though Apiary says it will convert OAI
json to yaml, that didn't work for me.
With this POM in place, a maven clean install will cause
a target/generated/swagger-ui/swagger.yaml
to be
generated, which is shown here. Personally, I don't even bother to
read this, since it's generated, but I am including it here for
completeness.
- swagger: "2.0"
- info:
- description: "Hello"
- version: "v20170204"
- title: "Hello Apiary"
- tags:
- - name: "HelloApiary"
- paths:
- /20170204:
- get:
- tags:
- - "HelloApiary"
- summary: "Simple echo of query string value"
- description: "If no query string is present return 500."
- operationId: "sayHello"
- produces:
- - "application/json"
- parameters:
- - name: "name"
- in: "query"
- description: "Your name"
- required: false
- type: "string"
- responses:
- 200:
- description: "Echo"
- 500:
- description: "Missing parameter"
Going Over the Top?
With any API of moderate complexity or more, this will be impossible to read. Which brings us to Apiary. Apiary came on my radar when my employer acquired them in mid January 2017. This is just one in a string of savvy acquisitions that I think puts Oracle in a great place for cloud dominance, provided we can integrate and execute successfully. Anyhow Apiary is awesome, and this whole blog post really is an on-ramp to further exploration of their stack.
Basically, you can take the swagger.yaml
generated
above, and paste it into the Apiary editor to get access to a well
integrated stack of features including testing, validation, and of
course documentation. The API is human browsable at
<http://docs.edburnshelloapiary.apiary.io/>
and Postman or other REST
tool interactable at <http://private-a5b54-edburnshelloapiary.apiary-mock.com/20170204?name=Foo>.
Apiary evolved around their own format, API Blueprint. API Blueprint is also an open standard with its own set of industry backers. It seems OAI has more momentum, though personally I prefer API Blueprint. Recognizing this, Apiary has solid support for OAI, but you constantly run into this friction when looking through the Apiary documentation. Keep that in mind when looking through the very decent tutorials at <https://help.apiary.io/api_101/>. But there is one interesting question they do not answer: which is better API Bluepring or OAI? Perhaps with the Oracle acquisition a definitive answer will be forthcoming? Mike Stowe has a formidable table comparing API Blueprint and OAI, as well as others.
Summary
Software development is a very heterogeneous practice now. The emphasis is on evolvability and agility, which means developers need to be familiar with lots of different tools, techniques, and stacks. Gone are the days where you could just learn one stack and continue to use it for all aspects of development. This blog post showed how to start with the familiar Java interface, add in JAX-RS and OAI annotations, use the swagger-maven-plugin to generate an artifact, and then uploade that to Apiary for further testing and exploration.