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.

  1. import javax.ws.rs.core.Response;
  2.  
  3. public interface HelloApiary {
  4.    
  5.     Response sayHello(String name);
  6.    
  7. }
  8.  

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:

  1. import javax.ws.rs.GET;
  2. import javax.ws.rs.Path;
  3. import javax.ws.rs.Produces;
  4. import javax.ws.rs.QueryParam;
  5. import javax.ws.rs.core.MediaType;
  6. import javax.ws.rs.core.Response;
  7.  
  8.  
  9. @Path("20170204")
  10. @Produces(MediaType.APPLICATION_JSON)
  11. public interface HelloApiary {
  12.    
  13.     @GET
  14.     Response sayHello(
  15.             @QueryParam("name") String name
  16.     );
  17.    
  18. }

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:

  1. import io.swagger.annotations.Api;
  2. import io.swagger.annotations.ApiOperation;
  3. import io.swagger.annotations.ApiParam;
  4. import io.swagger.annotations.ApiResponse;
  5. import io.swagger.annotations.ApiResponses;
  6. import javax.ws.rs.GET;
  7. import javax.ws.rs.Path;
  8. import javax.ws.rs.Produces;
  9. import javax.ws.rs.QueryParam;
  10. import javax.ws.rs.core.MediaType;
  11. import javax.ws.rs.core.Response;
  12.  
  13.  
  14. @Api("HelloApiary")
  15. @Path("20170204")
  16. @Produces(MediaType.APPLICATION_JSON)
  17. public interface HelloApiary {
  18.    
  19.     @ApiOperation(value = "Simple echo of query string value",
  20.             notes = "If no query string is present return 500.")
  21.     @ApiResponses(value = {
  22.         @ApiResponse(code = 200,
  23.                 message = "Echo",
  24.                 response = String.class),
  25.         @ApiResponse(code = 500,
  26.                 message = "Missing parameter",
  27.                 response = String.class)
  28.     })
  29.     @GET
  30.     Response sayHello(
  31.             @ApiParam(value = "Your name")
  32.             @QueryParam("name") String name
  33.     );
  34.    
  35. }

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.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3.     <modelVersion>4.0.0</modelVersion>
  4.  
  5.     <artifactId>20170204-apiary</artifactId>
  6.     <groupId>com.ridingthecrest</groupId>
  7.     <version>1.0</version>
  8.     <packaging>jar</packaging>
  9.     <name>Apiary example</name>
  10.  
  11.     <properties>
  12.       <maven.jar.plugin.version>2.6</maven.jar.plugin.version>
  13.       <maven.compiler.plugin.version>3.3</maven.compiler.plugin.version>
  14.       <swagger.version>1.5.10</swagger.version>
  15.       <javaee.api.version>7.0</javaee.api.version>
  16.       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  17.       <swagger-maven-plugin-version>3.1.1</swagger-maven-plugin-version>
  18.     </properties>
  19.  
  20.     <dependencies>
  21.         <dependency>
  22.             <groupId>io.swagger</groupId>
  23.             <artifactId>swagger-core</artifactId>
  24.             <version>${swagger.version}</version>
  25.         </dependency>
  26.         <dependency>
  27.           <groupId>javax</groupId>
  28.           <artifactId>javaee-api</artifactId>
  29.           <version>${javaee.api.version}</version>
  30.           <scope>provided</scope>
  31.         </dependency>        
  32.  
  33.     </dependencies>
  34.     <build>
  35.       <plugins>
  36.         <plugin>
  37.           <groupId>com.github.kongchen</groupId>
  38.           <artifactId>swagger-maven-plugin</artifactId>
  39.           <version>${swagger-maven-plugin-version}</version>
  40.           <configuration>
  41.             <apiSources>
  42.               <apiSource>
  43.                 <locations>com.ridingthecrest.apiary</locations>
  44.                 <info>
  45.                   <title>Hello Apiary</title>
  46.                   <version>v20170204</version>
  47.                   <description>Hello</description>
  48.                 </info>
  49.                 <outputPath>${project.build.directory}/generated/document.html</outputPath>
  50.                 <outputFormats>yaml</outputFormats>
  51.                 <swaggerDirectory>${project.build.directory}/generated/swagger-ui</swaggerDirectory>
  52.                 <swaggerApiReader>com.wordnik.swagger.jaxrs.reader.DefaultJaxrsApiReader</swaggerApiReader>
  53.               </apiSource>
  54.             </apiSources>
  55.           </configuration>
  56.           <executions>
  57.             <execution>
  58.               <phase>compile</phase>
  59.               <goals>
  60.                 <goal>generate</goal>
  61.               </goals>
  62.             </execution>
  63.           </executions>          
  64.         </plugin>        
  65.         <plugin>
  66.           <groupId>org.apache.maven.plugins</groupId>
  67.           <artifactId>maven-compiler-plugin</artifactId>
  68.           <version>${maven.compiler.plugin.version}</version>
  69.           <configuration>
  70.             <source>1.8</source>
  71.             <target>1.8</target>
  72.           </configuration>
  73.         </plugin>
  74.         <plugin>
  75.           <groupId>org.apache.maven.plugins</groupId>
  76.           <artifactId>maven-jar-plugin</artifactId>
  77.           <version>${maven.jar.plugin.version}</version>
  78.           <executions>
  79.             <execution>
  80.               <goals>
  81.                 <goal>test-jar</goal>
  82.               </goals>
  83.             </execution>
  84.           </executions>
  85.         </plugin>
  86.       </plugins>
  87.     </build>
  88.     <profiles>
  89.     </profiles>
  90. </project>
  91.  

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.

  1. swagger: "2.0"
  2. info:
  3.   description: "Hello"
  4.   version: "v20170204"
  5.   title: "Hello Apiary"
  6. tags:
  7. - name: "HelloApiary"
  8. paths:
  9.   /20170204:
  10.     get:
  11.       tags:
  12.       - "HelloApiary"
  13.       summary: "Simple echo of query string value"
  14.       description: "If no query string is present return 500."
  15.       operationId: "sayHello"
  16.       produces:
  17.       - "application/json"
  18.       parameters:
  19.       - name: "name"
  20.         in: "query"
  21.         description: "Your name"
  22.         required: false
  23.         type: "string"
  24.       responses:
  25.         200:
  26.           description: "Echo"
  27.         500:
  28.           description: "Missing parameter"
  29.  

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.