Writing APIs with the Swagger Inflector

Writing APIs with the Swagger Inflector

Author: Tony Tam

Swagger Inflector is a new project from the Swagger team for writing APIs on the JVM. It’s currently in preview state but is being actively developed and supported as an alternative, design-first way to code APIs in Java. Let’s walk through how you use the Inflector project.

[embed]http://www.slideshare.net/Swagger-API/the-inflector-project[/embed]

Getting Started

First and foremost, we start with a swagger specification. You can create a specification in your favorite text editor in JSON or YAML, or use the wonderful online editor at http://editor.swagger.io to build out the definition of your API. Don’t worry about making changes! We’ll walk through how Inflector helps you iterate quickly on your design without writing any code.

For this demo, we’ll assume that you’ve copied the Swagger description from here.

Next, we create a single YAML file for configuring the Inflector. This allows for a number of options, but for now, let’s just put the location of our Swagger description:

# inflector.yaml

swaggerUrl: ./src/main/swagger/swagger.yaml

Since under the hood the Inflector uses the Swagger Parser, you can point to local or remote files. Just specify a protocol of http or https if you are hosting your definition and the Swagger Parser will retrieve it. There are some very interesting use cases from this capability that we’ll cover in a later blog post.

Now let’s add the Inflector dependency. At runtime, Inflector simply uses Jersey 2.6 as the REST framework, so there are a lot of options for production integration. For now, let’s use the Jetty Maven Plugin and add Inflector as a dependency:

    <build>

<plugins>

<plugin>

<groupId>org.eclipse.jetty</groupId>

<artifactId>jetty-maven-plugin</artifactId>

<version>9.2.9.v20150224</version>

<configuration>

<monitoredDirName>.</monitoredDirName>

<scanTargets>

<scanTarget>inflector.yaml</scanTarget>

<scanTarget>src/main/swagger/swagger.yaml</scanTarget>

</scanTargets>

<scanIntervalSeconds>1</scanIntervalSeconds>

<webApp>

<contextPath>/</contextPath>

</webApp>

<httpConnector>

<port>8080</port>

<idleTimeout>60000</idleTimeout>

</httpConnector>

</configuration>

</plugin>

<!-- your other plugins -->

</plugins>

</build>

<dependencies>

<dependency>

<groupId>io.swagger</groupId>

<artifactId>swagger-inflector</artifactId>

<version>1.0.0-SNAPSHOT</version>

</dependency>

<!-- your other dependencies -->

Take a look at a sample pom.xml with everything you need.

Note that we configured two scan targets in the plugin configuration. That will tell Jetty to restart the application if either the ./inflector.yaml or the src/main/swagger/swagger.yamldescription files change. You’ll see how this makes development a snap with Inflector.

Finally, let’s add a web.xml. This lets us mount the Inflector application in the Jersey context, on the root of our service. We’re also going to add a CORS filter so we can read our Swagger API from Swagger UI easily.

<!-- from https://github.com/swagger-api/swagger-inflector/blob/master/samples/jetty-webxml/src/main/webapp/WEB-INF/web.xml -->

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<servlet>

<servlet-name>swagger-inflector</servlet-name>

<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>

<init-param>

<param-name>javax.ws.rs.Application</param-name>

<param-value>io.swagger.inflector.SwaggerInflector</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>swagger-inflector</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

<filter>

<filter-name>CORSFilter</filter-name>

<filter-class>io.swagger.inflector.utils.CORSFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>CORSFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

</web-app>

Note how we added the CORS filter. This is like any standard web.xml, you can add filters, static servlets, etc. No magic here!

That’s it for our project setup. Now let’s take it for a ride.

Launch the server:

mvn package jetty:run

And read the swagger listing on port 8080 (configured in the jetty plugin). Based on what you have configured in your basePath in your Swagger description, you will now be able to open up the Swagger description at http://localhost:8080/{basePath}/swagger.json (of course basePath can be omitted, per the spec). This will show you the specification, in JSON format, that you’ve been editing. I know, big deal, but we’re just getting started.

Notice that per our Jetty plugin configuration, you can save changes to your swagger.yamlfile and have automatic reload (it scans every second). One important point! If you write an invalid specification, the server will refuse to serve it up. Use the Swagger Editor for context-sensitive help in writing valid Swagger descriptions!

Now the interesting part. Let’s take a route from our description:

  /pet/{petId}:

get:

tags:

- pet

summary: Find pet by ID

description: Returns a single pet

operationId: getPetById

consumes:

- application/x-www-form-urlencoded

produces:

- application/xml

- application/json

parameters:

- name: petId

in: path

description: ID of pet to return

required: true

type: integer

format: int64

responses:

"200":

description: successful operation

schema:

$ref: "#/definitions/Pet"

"400":

description: Invalid ID supplied

"404":

description: Pet not found

If you hit up this endpoint (http://localhost:8080/v2/pet/1), Inflector will serve up an example payload for the #/definitions/Pet model in either JSON or XML, depending on your Accepts header. No code needed! Inflector will build the example from the swagger schema:

{

"id": 0,

"category": {

"id": 0,

"name": "string"

},

"name": "doggie",

"photoUrls": [

"string"

],

"tags": [

{

"id": 0,

"name": "string"

}

],

"status": "string"

}

That’s interesting! You can try the same thing off each operation in the Swagger description. Inflector will make Mocking a service simple. Again, as you modify your service description and make changes, the Jetty plugin will reload and show your changes immediately. You can even load the swagger-ui and point to our Swagger Description to see the real thing.

Now let’s move past Hello World and make this more real. Say you don’t want a mock response–you want to implement a controller and put in some business logic. Inflector will make this simple by routing the request directly to a controller.

This is configured with several techniques. Let’s walk through the options.

Explicitly naming the controller + package

You can use a Swagger Extension to add additional metadata into your description. For this use case, we’ll add the name of the controller class that we want requests sent to:

  /pet:

post:

tags:

- pet

summary: Add a new pet to the store

operationId: addPet

# We're configuring Inflector to look in this class for our controller!

x-swagger-router-controller: io.swagger.sample.SampleController

Now at start-up, Inflector will look in the class io.swagger.sample.SampleController for a method matching this method. But what is our signature???

Inflector will use the operationId as the method name. If it does not exist, there is an algorithm to create one (you should just specificy it to make life easier). It will then look for parameter singatures matching the input parameters to the operation PLUS one extra parameter for context. In fact, if you configure your application to start with debug logging, you’ll see what it’s trying to find:

DEBUG i.s.i.SwaggerOperationController - looking for operation: addPet(io.swagger.inflector.models.RequestContext request, com.fasterxml.jackson.databind.JsonNode body)

Note the RequestContext as the first parameter–it contains valuable information about headers, media types, etc. Take a look at the source to see what it provides you.

Note that the body is mapped directly to a JsonNode object. This gives you a Map or Array-type object to treat as your domain model (this holds true for XML as well).

Locating naming the controller + package automatically

For the brave, you can configure Inflector to scan a controllerPackage:

# inflector.yaml

controllerPackage: io.swagger.sample.controllers

Where Inflector will always look. You can also use the controllerPackage to set the default package lookup, and set the x-swagger-router-controller: SampleController instead of using a FQ name.

Then, the inflector will use the FIRST tag to construct the class name. If you have an operation tagged:

tags:

- pet

Inflector will now take the controllerPackage + ‘.’ + Capitalize(tags[0]) + Controller as the expected class name:

io.swagger.sample.controllers.PetController

Implementing business logic

Implementing the business logic is as simple as creating that method in our controller:

package io.swagger.sample;

import io.swagger.inflector.models.RequestContext;

import io.swagger.inflector.models.ResponseContext;

import com.fasterxml.jackson.databind.JsonNode;

import javax.ws.rs.core.Response.Status;

public class SampleController {

// the ResponseContext is optional, but it gives extra control about writing to the client

public ResponseContext addPet(RequestContext request, JsonNode body) {

return new ResponseContext()

.status(Status.OK)

.entity(body);

}

}

Boring, yes, but this shows how we now have complete control over the response object, and how Inflector has mapped directly to our controller! It’s pretty amazing how fast you can write a REST API when you’re not monkeying around with all the plumbing!

Mapping POJOs

Let’s say that we want a concrete object instead of using JsonNode. This is easy to do–you have two choices.

First, you can map the model directly in the inflector.yaml using the modelMappings syntax:

modelMappings:

Pet: io.swagger.sample.models.Pet

You guessed it, when we see a Pet definition in the Swagger description, we’ll use theio.swagger.sample.models.Pet implementation.

Second, you can configure a modelPackage and Inflector will try to find it:

modelPackage: io.swagger.sample.models

This will cause Inflector to load io.swagger.sample.models.Pet from the class loader. If it exists in EITHER case, your method signature will look a lot more friendly:

DEBUG i.s.i.SwaggerOperationController - looking for operation: addPet(io.swagger.inflector.models.RequestContext request, io.swagger.sample.models.Pet body)

Now you can start working with more familiar looking code:

  public ResponseContext addPet(RequestContext request, Pet body) {

/* all your business logic */

}

Conclusion

This has been a fairly long post but hopefully the design-first goal of the Inflector is clear. Once you can start writing APIs without worrying about all the plumbing, you’ll find that you’ll create better APIs, and much more quickly. Inflector takes away the annotations and other wiring necessary to produce Swagger descriptions, and puts Swagger itself in the role of the DSL for the REST API.