API-first meets Java

      No Comments on API-first meets Java

Introduction

In this workshop, we’re going to define a REST API and implement it in Java. Furthermore, we’re going to do it using the API-first methodology, which means we’re going to first define the API in a language-independent fashion (using OpenAPI) and then we’re going to implement that API in Java.

Why would we do this? In the API-first methodology, the API exists independently from its implementation. There are a number of good reasons for doing this, the most important one being that the API represents a contract with its clients, which must maintain its integrity over time and therefore must be free of implementation details. In order to avoid getting stuck on a specific technology stack, it must be possible to take an existing API and reimplement it on a different technology stack (e.g. Go, Python, Javascript) without breaking the API contract the clients depend on. Additional benefits include being able to take advantage of documentation and test tools which are available to APIs defined using standards like OpenAPI.

Why use Java? After all, there are a number of other languages which are better suited to implementing REST APIs. The answer is that Java is still the most important language for enterprise development and therefore there are many situations where it makes sense to implement REST services in Java. Luckily, tools like Spring Boot and the OpenAPI/Swagger Java Inflector make it relatively straightforward to use the API-first methodology in Java.

Whats wrong with the “API-second” approach: The traditional approach (let’s call it the API-second approach) is to publish an existing Java service using Jersey or some other REST framework. However, this approach turns out to be a bad idea in practice since it’s almost impossible to avoid Java-specific details leaking into the API and later changes to the implementation may inadvertently break the API.

The technology stack used in this workshop

  • Java
  • Spring Boot (Java application server)
  • Swagger Inflector (to wire the API to Java)
  • Visual Studio Code (Editor/IDE)
  • RestAssured.io (to test the api)

The code for the workshop is available on GitHub at:

https://github.com/armstrongconsulting/code-samples/tree/master/spring-boot-api-first-demo

Step 1: Design a first version of your API.

The online swagger editor is a great tool for defining the API. Point your browser at https://editor.swagger.io, Choose the “File/Import Url” and enter “https://petstore.swagger.io/v2/swagger.json”, since for the purposes of this workshop, we’ll implement the well-known “petstore” API. With the online editor you can very quickly create your own first draft of your API. When you are satisfied, download a YAML version of your API and store it for later use and update.

Step 2: Boot up your framework with Spring Initilizr in Visual Studio Code

Create a new spring-boot project, as described here: https://code.visualstudio.com/docs/java/java-spring-boot. Use the latest spring boot version (we used 2.1.1 when creating this workshop) and add the “Web” and “Jersey (JAX-RS)” dependencies (since swagger inflector is based on jersey).

Add the swagger inflector dependency to the pom.xml.

<dependency>
  <groupId>io.swagger</groupId>
  <artifactId>swagger-inflector</artifactId>
  <version>1.0.15</version>
</dependency>

Note: The 1.xx branch of swagger-inflector is only compatible to Open-Api Version 2, the 2.xx branch is currently under development and will support OAS3 as well.

Step 3: Run an implementation of your API with swagger-inflector, explore it with swagger-ui

In order that swagger inflector can interpret your API, follow these steps:

Copy the downloaded yaml file to src/main/ressources/swagger.yaml

Configure  jersey to load the swagger inflector resource. We have combined all relevant code in one configuration class, which you’ll  find here (SwaggerConfig.java). The “SwaggerConfig.java” additionally configures CORS so that your API can be contacted from anywhere.

Configure some important application.properties wich

  • Boot up the server at port 8080
  • Configure some important Jackson properties so that dates are properly serialised
  • set the jersey application path to /api

Allow your application to serve static content (e.g. the swagger-ui application) by creating a WebConfig.java

The artefacts for swagger-ui are brought in via webjars simply by adding the following to the pom.xml

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>swagger-ui</artifactId>
  <version>3.20.3</version>
</dependency>

If you run your application now, swagger ui should boot up under the following url

http://localhost:8080/webjars/swagger-ui/3.20.3/index.html?url=/api/v2/swagger.yaml

(Note: this assumes that the “basePath” in swagger.yaml is set to /v2!)

Step 4: Implement your api

The model

Swagger generator is a great tool to save us from a lot of boilerplate.  Together with maven it will help you build your model. Adding the the swagger-codegen-maven-plugin and the build-helper-maven-plugin to the pom.xml does the magic. For details see the pom.xml on GitHub

The controller

Other swagger frameworks depend on generating the api specification from the implementation, or to generate the implementation from the api specification. Swagger inflector takes an alternative approach and interprets the swagger.yaml file on the fly and routes the request to a controller.

Here is our example of implementing one api method:

@Component
public class PetController{

    public ResponseContext getPetById(RequestContext request, Long petId){
        Pet pet = new Pet()
        		.id(petId)
        		.name("Demo Pet");
        return new ResponseContext()
                   .status(200)
                   .entity(pet);
    }
}

The sample implementation simply takes the provided Id, adds a dummy name and returns the “demo pet”. Since swagger inflector is running in “DEVELOPMENT” mode (see SwaggerConfig.java), it automatically provides dummy implementations for all other methods.

Step 5: Test your API

We have chosen rest-assured to test our APIs in unit and integration tests, since it’s simple and intuitive (see pom.xml which shows which artefact will be required in the test scope). The following unit test verifies that our PetController returns a valid result when fetching a pet:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class PetControllerTests {

  @LocalServerPort
  protected int port;

  @Before
  public void setup() {
    RestAssured.port = port;
    RestAssured.baseURI = "http://localhost/api/v2";
  }

  @Test
  public void fetchPetTest() {
    Pet pet = given()
      .contentType("application/json") 
    .when()
      .get("/pet/12345")
    .then().assertThat()
      .statusCode(200)
      .and()
        .extract()
        .jsonPath()
        .getObject("",Pet.class);

    Assert.assertNotNull(pet);
    Assert.assertEquals(Long.valueOf(12345L), pet.getId());
    Assert.assertEquals("Demo Pet", pet.getName());

    }

}

Conclusion

Its perhaps not the easiest way to publish a REST API, but combining Java and the API-first methodology provides the best of both worlds – the correctness of an API defined using the OpenAPI standards and the performance, static typing and enterprise development ecosystem of Java.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.