Java Ecosystem, Kotlin, Distributed Systems, Sociology of Software Development

Enriching RESTful Services with Swagger

Posted on Jul 28, 2015

Consuming RESTful services can be a laborious task, because there is much low-level-work to do. Jealously we looked at the WS*/SOAP guys: They can easily generate a nice client API based on the formal interface specification WSDL. This significantly simplifies the service consumption. For a long time the REST world lacks a widespread formal specification and generation tools. But Swagger sets out to change this.

Enriching RESTful Services with Swagger

Problems when Dealing with RESTful Services

When is comes to consuming a RESTful service you are facing two problems:

  • You have to read and understand the human-readable API documentation of the RESTful service.
  • Laborious service consumption due to low-level-work. You have to create an HTTP request with the right HTTP method, the right HTTP headers on the right URL with the right parameters and the correct JSON in the HTTP body. Especially when the service API changes, the maintenance effort is high.

When developing and maintaining a RESTful service you have the following problems:

  • You have to manually write an API documentation.
  • When you change your service you have to take care of updating the documentation, which is an error-prone task.

Swagger can solve these problems.

Approaches for Using Swagger

swagger-logo

The heart of Swagger is a formal language for describing RESTful services. But most important, it provides a powerful and actively developed ecosystem of tools around this formal specification like code generators and editors. That’s the unique selling point in contrast to other REST specification languages like WADL.

There are several ways to use Swagger in the development workflow:

  • API first approach
  • Service first approach

API First Approach

You can start and write your Swagger API specification first. Based on this specification you can generate both JAX-RS stub resource classes and a client library for consuming the service. Although you can utilize the Swagger Editor for creating the specification, which is a laborious task and you have to learn the specification language.

However, this is the way to go, when you kick off a new project involving multiple teams of different companies and you have to commit to an API specification up front. This way all teams can start working and don’t have to wait for the service team (e.g. by generating a dummy implementation for the service).

A young, but promising tool for this purpose is swagger-inflector (thanks to Michael for the tip). It eases the implementation of a JAX-RS server based on a given swagger file, because there is no JAX-RS generation step anymore. Inflector takes care about the wiring and redirects the requests to your business logic at runtime. If you don’t provide business logic, dummy entries are returned. Hence, the Inflector can also be used for mocking a service for a given swagger file.

Service First Approach

I prefer the service first approach which is more pragmatic. Consider the following illustration of the workflow:

Usage of Swagger when developing the service first

Usage of Swagger when developing the service first

Update: Please also check out my blog post “RESTful API Documentation with Swagger and AsciiDoc”. It covers an enhanced approach using AsciiDoc. This way, you can combine manually written and generated documentation.

The Service Side

Usually, you start developing your RESTful service by writing JAX-RS resource classes. Next, you provide additional documentation about your RESTful API by adding Swagger annotations to your resource class. Most of the Swagger annotations start with the prefix @Api*. A resource class could finally looks like this:

@Api(value = "customers", description = "RESTful API to interact with customer resources.")
@Path("customers")
public class CustomerResource {

    @Inject
    private CustomerDAO dao;

    @ApiOperation(value = "Get all customers", notes = "Get all customers matching the given search string.", responseContainer = "List", response = Customer.class)
    @GET
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    public List<Customer> getCustomers(
            @ApiParam(value = "The search string is used to find customer by their name. Not case sensetive.", required = false, defaultValue = "") @QueryParam("search") String searchString,
            @ApiParam(value = "Limits the size of the result set", required = false, defaultValue = "50") @QueryParam("limit") int limit) {
        List<Customer> customers = dao.getCustomers(searchString, limit);
        return customers;
    }

    @ApiOperation(value = "Create a new customer", notes = "Creates a new customer with the given name. The URL of the new customer is returned in the location header.")
    @POST
    @Path("/")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createEmployee(
            @ApiParam(value = "customer's name", required = true) String customerName)
            throws URISyntaxException {
        Customer customer = dao.createCustomer(customerName);

        URI uri = createNewLocationURI(customer.getId());
        Response response = Response.created(uri).build();
        return response;
    }
}

Now we can let Swagger process our annotated classes. It will generate the formal specification. Typically an HTML documentation for your API based on the specification will be created in the same step. There are several ways to achieve that:

  • You can generate the swagger specification and documentation during the maven build. This can be done with the swagger-maven-plugin.
  • If you are using Dropwizard you can use the bundle dropwizard-swagger. It will automatically generate the swagger specification and documentation based on your classes during startup of the service and publish both under certain URLs. That’s very nice, because this way you ensure that your specification and documentation is always up to date with your published service. Besides you don’t have to care about how you publish the specification and documentation.

The generated API specification for our RESTful service looks like this:

swagger: "2.0"
info: {}
basePath: "/"
tags:
- name: "customers"
paths:
  /customers:
    get:
      tags:
      - "customers"
      summary: "Get all customers"
      description: "Get all customers matching the given search string."
      operationId: "getCustomers"
      consumes:
      - "application/json"
      produces:
      - "application/json"
      parameters:
      - name: "search"
        in: "query"
        description: "The search string is used to find customer by their name. Not\
          \ case sensetive."
        required: false
        type: "string"
      - name: "limit"
        in: "query"
        description: "Limits the size of the result set"
        required: false
        type: "integer"
        default: "50"
        format: "int32"
      responses:
        200:
          description: "successful operation"
          schema:
            type: "array"
            items:
              $ref: "#/definitions/Customer"
    post:
      tags:
      - "customers"
      summary: "Creates a new customer"
      description: "Creates a new customer with the given name. "
      operationId: "createEmployee"
      consumes:
      - "application/json"
      parameters:
      - in: "body"
        name: "body"
        description: "customer's name"
        required: false
        schema:
          type: "string"
      responses:
        default:
          description: "successful operation"

The generated documentation:

The generated Swagger documentation for our RESTful service

The generated Swagger documentation for our RESTful service

What are the benefits? We get a nice (and interactive!) documentation for our API which is automatically kept up to date. It is generated, so we don’t have to write it manually. Moreover, we provide a formal specification of our API. This specification can be used to generate a client library (in multiple programming languages). This is what we will do next.

The Client Side

Clients who want to consume our RESTful service can use our published Swagger specification to generate a nice client library. There are several approach to do the generation:

Command Line Tool

We use the command line tool swagger-codegen for this purpose. Here is an example of how to use it:

java -jar modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate \
  -i http://localhost:8080/swagger.json \
  -l java \
  -o samples/client/customer/java

This generates a full new maven project, which we can build and install into our repository.

Maven Plugin

However, I recommend to execute the generation in the maven build via the swagger-codegen-maven-plugin within an existing project. This way, you can maintain your own pom for your project (not the generated one) and gain full control over the build configuration (control SVN/Git location, artifact repository URL). This fits better into a typical build infrastructure.

<properties>
    <yaml.file>${project.basedir}/src/main/resources/prozu-service.yaml</yaml.file>
    <generated-sources-path>${project.build.directory}/generated-sources</generated-sources-path>
    <generated-sources-java-path>main/java</generated-sources-java-path>
    <version.swagger.codegen>2.1.4</version.swagger.codegen>
    <!-- TODO add the properties from target/generated-sources/pom.xml here -->
</properties>

<dependencies>
    <!-- TODO add the dependencies from target/generated-sources/pom.xml here -->
</dependencies>

<build>
<plugins>
    <plugin>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-codegen-maven-plugin</artifactId>
        <version>${version.swagger.codegen}</version>
        <configuration>
            <inputSpec>${yaml.file}</inputSpec>
            <configOptions>
                <sourceFolder>${generated-sources-java-path}</sourceFolder>
            </configOptions>
            <output>${generated-sources-path}</output>
        </configuration>
        <executions>
            <execution>
                <id>generate-swagger-javaclient</id>
                <phase>generate-sources</phase>
                <goals>
                    <goal>generate</goal>
                </goals>
                <configuration>
                    <language>java</language>
                    <modelPackage>${groupId}.prozu.client.model</modelPackage>
                    <apiPackage>${groupId}.prozu.client.api</apiPackage>
                    <invokerPackage>${groupId}.prozu.client.invoker</invokerPackage>
                </configuration>
            </execution>
        </executions>
    </plugin>
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>build-helper-maven-plugin</artifactId>
        <version>1.9.1</version>
        <executions>
            <!-- TODO for eclipse/m2e users: install the m2e connector 'buildhelper' by selecting 'Discover new m2e connectors' while hovering over the follwoing execution tag -->
            <execution>
                <id>add-generated-source</id>
                <phase>initialize</phase>
                <goals>
                    <goal>add-source</goal>
                </goals>
                <configuration>
                    <sources>
                        <source>${generated-sources-path}/${generated-sources-java-path}</source>
                    </sources>
                </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>

<!-- the following is only necessary if you are using eclipse and m2e -->
<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.eclipse.m2e</groupId>
            <artifactId>lifecycle-mapping</artifactId>
            <version>1.0.0</version>
            <configuration>
                <lifecycleMappingMetadata>
                    <pluginExecutions>
                        <pluginExecution>
                            <pluginExecutionFilter>
                                <groupId>io.swagger</groupId>
                                <artifactId>swagger-codegen-maven-plugin</artifactId>
                                <versionRange>[${version.swagger.codegen},)</versionRange>
                                <goals>
                                    <goal>generate</goal>
                                </goals>
                            </pluginExecutionFilter>
                            <action>
                                <execute />
                            </action>
                        </pluginExecution>
                    </pluginExecutions>
                </lifecycleMappingMetadata>
            </configuration>
        </plugin>
    </plugins>
</pluginManagement>
</build>

Running mvn generate-sources will generate the client library code into the project under target/generated-sources. Calling mvn package will create a jar containing the generated client library code.

Usage

Afterwards we can use the library in our application consuming the service.

ApiClient apiClient = new ApiClient();
apiClient.setBasePath("http://localhost:8080");
CustomersApi customerApi = new CustomersApi(apiClient);
List<Customer> customers = customerApi.getCustomers("peter", 40);

This significantly simplifies the consumption of a RESTful service, because we use an abstraction layer. The low-level work is done by the library.