Add a serverless flavour to Vert.x (part 1)

In this post (or better to say in series of two posts), we would add some serverless flavour to your Vert.x apps. We would implement an API gateway pattern with Vert.x and Java, and than we would connect it with a function. In the first post we create the gateway itself, and in the second post we would build a serverless function, deploy it and connect with Vert.x.

API Gateway pattern

You can read about this approach in the Mike Robert’s article. The idea is simple – however let me quote the author directly:

An API gateway is an HTTP server where routes and endpoints are defined in configuration, and each route is associated with a resource to handle that route. In a Serverless architecture such handlers are often FaaS functions.

What does a typical API gateway do? It acts as a normal router, so when it receives the request, it matches the action to it, just like any other route. But there, this action is the function. Additionally it does authentication, validation, response code mapping etc.

You could think that this is a logical extension of the microservices architecture and this is true. Take a look on the graph below:

When the client sends its request, for example GET, it goes to the API gateway. Next, API gateway checks it (e.g. performs security features, validation etc) and matches to the specific function. Finally, the function will compute some logic and return a result back to the API gateway, which in turn will transform the result as a valid HTTP response (with all response codes and other stuff) and send it back to the client.

Sounds easy enough, right? The only question is could we implement it with Vert.x? I think the answer is positive. Don’t just rely on the vendor specific API gateway, but build your own cloud-agnostic API gateway!

Create a new project

In this part we would start from the API gateway itself. It is a simple Vert.x verticle, that provides one POST route. User sends a list of products she/he buys as a JSON data and receives back a bill = sum of all products. In this tutorial we would use Vert.x starter to generate a new empty project. Go to start.vertx.io:

We need just one dependency – Vert.x-Web – to build a router. Select it and fill required fields. Then download a generated result. If you use Intelij, it is very easy to import it. Click Import project and select Maven as an external model. Then just click Next several times, as default config is good enough and you don’t need usually to change it.

How to call functions from the Service

In order to use functions inside the Vert.x service we need to build some sort of abstraction first. Nothing is better for this than an old good factory pattern. We need to have Function interface, that represents abstract function and FunctionFactory to create new functions. Also we would have FunctionResult that wraps results of computation. Check the graph below:

Basically, we do these steps:

  1. Caller asks Factory to create an instance of Function by name
  2. Factory sends back to the Caller a new Function
  3. When we execute compute() method on some data, we would obtain a FunctionResult

Let observe them in details.

Function interface

Function is a one-method interface to abstract computations. We supply to it data in a form of universal JsonObject and get the result:

public interface Function {

  FunctionResult compute (JsonObject input);

}

We use a dedicated FunctionFactory class to create new functions. As functions utilize shared resources, like http connections, we provide a factory to re-use these components.

FunctionResult

Function returns a result of computing. In a nutshell, it is also a JsonObject, however we need to assert somehow is result successful? We can do it with Optional interface, however, I think it is a good idea to continue with another approach. It comes from Vert.x API, so it is incorporated natively to it. We have a method success() that returns True for successful computation, and False if there was a problem:

public class FunctionResult {

  private JsonObject data;
  private boolean success;

  public FunctionResult(JsonObject data, boolean success){
    this.data = data;
    this.success = success;
  }

  public JsonObject result(){
    return data;
  }

  public boolean success(){
    return success;
  }
}

Route handler

Now, when we have both a factory and a function interface, we can incorporate them into route:

private void getBill (RoutingContext context){
   //get data
   JsonObject order = context.getBodyAsJson();

   //call function
   Function calculateBillFunction = functionFactory.create("calculate-bill");
   FunctionResult billAmount = calculateBillFunction.compute(order);

   //return back
   if (billAmount.success()){
     context.response().setStatusCode(200).end(billAmount.result().encodePrettily());
   } else {
     context.response().setStatusCode(500).end("Server error");
   }
}

You could observe, that our API gateway is completely stateless. We use it to hide an underlaying function, but it does not proceed any other operation itself. We get user data as JSON and then send it to created function. Finally, we do request code matching, but again we don’t have any assertions inside route – we trust function in this regard.

Configure router

The only remaining thing is to configure a router. There is nothing specific here, so if you follow my Vert.x microservices tutorial, you can easily implement it. NB!: don’t forget to create a BodyHandler first.

@Overrride
public void start(){
  HttpServer server = vertx.createHttpServer();
  Router router = Router.router(vertx);
  router.route("/api/*").handler(BodyHandler.create());
  router.post("/api/bill").consumes("application/json").handler(this::getBill);
  server.requestHandler(router).listen(4567, res->{
    if (r.failed()){
      System.out.println("Unable to create server: "+r.fail().cause().getLocalizedMessage());
    } 
  });
}

Conclusion

That’s all, folks (for now)! In this post we did a good work: we observed what is an API gateway and how to start with it in Vert.x. In the next part we would continue and would create our first function and connect it with our API gateway.

References

  • Ashan Fernando. Things to consider before building a serverless data warehouse (2018), read here
  • Bill Doerrfeld. The Benefits of a Serverless API backend (2017), read here
  • Mike Roberts. Serverless Architecture (2018),read here