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.yaml
description 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.yaml
file 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.