Contract Driven REST Services with Vert.x3

We see a new trend in de­vel­op­ment where we are shift­ing from de­vel­op­ing ap­pli­ca­tions to de­velop APIs. More and more we see ser­vices being of­fered as REST APIs that we are al­lowed to con­sume and we al­ready see this trend from the big names in the in­dus­try, e.g.: Face­book, Wikipedia, Ama­zon, Twit­ter, Google and Red­dit they all offer APIs.

Of course mak­ing an REST API using Vert.x is quite sim­ple, just use Vert.x Web and you can start writ­ing your API in sec­onds, how­ever an API with­out doc­u­men­ta­tion is not an API since no de­vel­oper will know how to use it. How­ever this is one of the most sen­si­tive is­sues to tackle in soft­ware de­vel­op­ment, no­body likes to write doc­u­men­ta­tion. How­ever, in the REST age where REST-​based web-​services are ubiq­ui­tous, doc­u­men­ta­tion for pub­lic web-​services is a ne­ces­sity. There are a lot of tools out there, two of the most pop­u­lar are with­out a doubt:

Both frame­works have a large ecosys­tem of tools and tool­ing around but they tackle the doc­u­men­ta­tion from two dif­fer­ent per­spec­tives. While Swag­ger is purely a doc­u­men­ta­tion tool it tack­les the prob­lem from bot­tom up and RAML does doc­u­ment top down. They both rely on a doc­u­ment (JSON for Swag­ger, YAML for RAML).

I’ll now go over build­ing a sim­ple Hello World API, doc­u­ment it and test it. For this ex­am­ple I’ll choose RAML since it feels more nat­ural to the way we code with Vert.x Web.

Define the Hello World API

We need a con­tract and as with any other de­vel­op­ment we need some spec­i­fi­ca­tion, You can learn about RAML in its web­site and quickly see how easy it is to get started. So we start with the fol­low­ing spec­i­fi­ca­tion:

#%RAML 0.8
title: Hello world REST API
baseUri: http://localhost:8080/
version: v1
/hello:
  get:
    responses:
      200:
        body:
          application/json:
            schema: |
              { "$schema": "http://json-schema.org/schema",
                "type": "object",
                "description": "Hello World Greeting",
                "properties": {
                  "greeting":  { "type": "string" }
                },
                "required": [ "greeting" ]
              }

So if you didn’t un­der­stood why I named RAML as a top down doc­u­men­ta­tion tool, I think it be­comes clear now. So there are some basic de­f­i­n­i­tion on the top of the file like, title, baseUri and version which should be self ex­plana­tory.

And then we start with the API doc­u­men­ta­tion, so we spec­ify that at the URL /hello using the HTTP verb GET you are ex­pected to get a re­sponse with sta­tus code 200 and the body of the re­sponse should have con­tent type application/json. This is a very min­i­mal doc­u­ment, one could go over and spec­ify the json schema for the re­sponse, input val­ues, etc…, how­ever lets just keep it sim­ple for this ex­am­ple.

If you do not like to write yaml in your ed­i­tor you can al­ways use the API De­signer which gives you in­stant feed­back on your API doc­u­ment and pro­vides a test­ing plat­form.

Implement the API

So you got your con­tract, time to im­ple­ment it, this is a very sim­ple API so I’ll jump to the code im­me­di­ately:

public class App extends AbstractVerticle {
  @Override
  public void start() {
    Router router = Router.router(vertx);

    router.get("/hello").handler(rc -> {
      rc.response()
          .putHeader("content-type", "application/json")
          .end(new JsonObject().put("greeting", "Hello World!").encode());
    });

    vertx.createHttpServer().requestHandler(router::accept).listen(8080);
  }
}

As you can see the code re­sem­bles the con­tract doc­u­ment, when there is a GET re­quest to /hello we send to the client a empty JSON doc­u­ment {}.

Are we done?

The an­swer is NO!!! how can we be sure that our im­ple­men­ta­tion does com­ply to the con­tract? We need to test. As I wrote be­fore there is no spe­cific sup­port for Vert.x from RAML or other tools how­ever in this case it is not a prob­lem we can still test our code with­out hav­ing the need to im­ple­ment a test frame­work from the ground up.

Testing our contract

The ini­tial step is to setup a test, this should be triv­ial we are using Java (al­though we could test any of Vert.x sup­ported lan­guages using the same tech­nique). We cre­ate a JUnit unit test.

For this ex­am­ple, I will be using JUnit in­stead of Vert.x Test mostly to let you know that Vert.x isn’t an opin­ion­ated frame­work, so you are free to choose the tool that best fits you.

public class APITest {

  @BeforeClass
  public static void bootApp() {
    Runner.run(App.class);
  }

  @Test
  public void testHelloEndpoint() {
  }
}

So at this mo­ment you have a sim­ple test, I’ll share the code of the run­ner class (ba­si­cally it just in­stan­ti­ates a Vertx in­stance and load the verticle we just im­ple­mented above) and has a empty test testHelloEndpoint.

Load the API definition into the test

public class APITest {

  private static final RamlDefinition api = RamlLoaders.fromClasspath()
        .load("/api/hello.raml")
        .assumingBaseUri("http://localhost:8080/");

  private CheckingWebTarget checking;

  ...
}

So the first step is to load the API de­f­i­n­i­tion into our test and have a ref­er­ence to a CheckingWebTarget ob­ject. The check­ing ob­ject is where you can per­form as­ser­tions, but to do this we need some client make REST calls in order to test.

Create a REST client

There are many op­tions for this, you could use JAX-RS, RestAssured, RestEasy, etc… so I’ll pick RestEasy for now:

public class APITest {

  private static final RamlDefinition api = RamlLoaders.fromClasspath()
      .load("/api/hello.raml")
      .assumingBaseUri("http://localhost:8080/");

  private ResteasyClient client = new ResteasyClientBuilder().build();
  private CheckingWebTarget checking;

  @BeforeClass
  public static void bootApp() {
    Runner.run(App.class);
  }

  @Before
  public void createTarget() {
    checking = api.createWebTarget(client.target("http://localhost:8080"));
  }

  ...
}

Implement the test

All of the boil­er­plate code is in place and if you look at the pre­vi­ous sec­tion you will see that it wasn’t that bad, just a few lines and you loaded the RAML con­tract, cre­ated a REST client and started up your ap­pli­ca­tion and all this under ~10 lines of code.

So lets fin­ish and im­ple­ment the ver­i­fi­ca­tion of the con­tract:

public class APITest {
  ...

  @Test
  public void testHelloEndpoint() {
    checking.path("/hello").request().get();
    Assert.assertThat(checking.getLastReport(), RamlMatchers.hasNoViolations());
  }
}

Once you run your tests, you will see:

13:09:28.200 [main] DEBUG o.a.h.i.conn.DefaultClientConnection - Sending request: GET /hello HTTP/1.1
13:09:28.201 [main] DEBUG org.apache.http.wire -  >> "GET /hello HTTP/1.1[\r][\n]"
13:09:28.202 [main] DEBUG org.apache.http.wire -  >> "Accept-Encoding: gzip, deflate[\r][\n]"
13:09:28.202 [main] DEBUG org.apache.http.wire -  >> "Host: localhost:8080[\r][\n]"
13:09:28.202 [main] DEBUG org.apache.http.wire -  >> "Connection: Keep-Alive[\r][\n]"
13:09:28.202 [main] DEBUG org.apache.http.wire -  >> "[\r][\n]"
13:09:28.202 [main] DEBUG org.apache.http.headers - >> GET /hello HTTP/1.1
13:09:28.202 [main] DEBUG org.apache.http.headers - >> Accept-Encoding: gzip, deflate
13:09:28.203 [main] DEBUG org.apache.http.headers - >> Host: localhost:8080
13:09:28.203 [main] DEBUG org.apache.http.headers - >> Connection: Keep-Alive
13:09:28.412 [main] DEBUG org.apache.http.wire -  << "HTTP/1.1 200 OK[\r][\n]"
13:09:28.413 [main] DEBUG org.apache.http.wire -  << "content-type: application/json[\r][\n]"
13:09:28.413 [main] DEBUG org.apache.http.wire -  << "Content-Length: 2[\r][\n]"
13:09:28.413 [main] DEBUG org.apache.http.wire -  << "[\r][\n]"
13:09:28.414 [main] DEBUG o.a.h.i.conn.DefaultClientConnection - Receiving response: HTTP/1.1 200 OK
13:09:28.414 [main] DEBUG org.apache.http.headers - << HTTP/1.1 200 OK
13:09:28.415 [main] DEBUG org.apache.http.headers - << content-type: application/json
13:09:28.415 [main] DEBUG org.apache.http.headers - << Content-Length: 2
13:09:28.429 [main] DEBUG org.apache.http.wire -  << "{}"
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.076 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

And we are done, we now have a API that fol­low the con­tract, you can now keep de­vel­op­ing your API and im­ple­men­ta­tion and have a test dri­ven ap­proach to be sure that the con­tract is not bro­ken.

Next steps

Until now you have learn how to in­te­grate RAML into Vert.x and CI, how­ever the users of your API will not be able to know much about the API yet since its doc­u­men­ta­tion is not pub­licly avail­able. So lets pub­lish on­line the doc­u­men­ta­tion of your API, of course if your API is pri­vate you do not need to fol­low these steps.

In order to do this all we need it to in­clude in our ap­pli­ca­tion the RAML con­sole, the fastest way to do this is just down­load a re­lease to src/main/resouces/webroot and in the orig­i­nal ap­pli­ca­tion Vert.x Router we add a Sta­tic Con­tent Han­dler to serve the con­sole files. Your ap­pli­ca­tion source code should look like this:

public class App extends AbstractVerticle {
  @Override
  public void start() {
    Router router = Router.router(vertx);

    router.get("/hello").handler(rc -> {
      rc.response()
          .putHeader("content-type", "application/json")
          .end(new JsonObject().put("greeting", "Hello World!").encode());
    });

    // optionally enable the web console so users can play with your API
    // online from their web browsers
    router.route().handler(StaticHandler.create());

    vertx.createHttpServer().requestHandler(router::accept).listen(8080);
  }
}

Once you start you ap­pli­ca­tion open a browser point­ing at the con­sole. Once you do that you should be pre­sented with some­thing sim­i­lar to this:

apiconsole

Article source code

You can get the full source code for this ar­ti­cle here.

Next post

Vert.x 3.1.0 is released!

I'm pleased to announce the release of Vert.x 3.1!

Read more
Previous post

Writing secure Vert.x Web apps

This is a starting guide for securing Vert.x web applications. Standard rules and practices apply to Vert.x apps as if they would to any other web framework.

Read more
Related posts

Unit and Integration Tests

Let’s refresh our mind about what we developed so far in the introduction to vert.x series. We forgot an important task. We didn’t test the API.

Read more

Some Rest with Vert.x

This post is part of the Introduction to Vert.x series. Let’s go a bit further this time and develop a CRUD-ish application

Read more

Combine vert.x and mongo to build a giant

This blog post is part of the introduction to Vert.x series. We are now going to replace this JDBC client by the vertx-mongo-client, and thus connect to a Mongo database.

Read more