Skip to content

Build a micro service application with Vertx: a complete tutorial (part 4/5)

In this series of posts, I would teach you, how to create a complete reactive micro service application with Vert.x and Java, connect it with your data source, test and deploy on Heroku platform.

Table of contents

AppEndpoint functions

What is the AppEndpoint and why do we need to create it? If you remembered on our application architecture, you noticed, that the AppEndpoint exposes REST API for external clients, such mobile and web (or maybe desktop or embedded) applications. In the classical micro services architecture, the role of AppEndpoint corresponds to API Gateway.

What does AppEndpoint?

  1. Creates a web server, that is listening the specific port
  2. Exposes routes for HTTP requests, such as GET, POST, PUT, DELETE
  3. Handles an authentication/authorization. NB!: Although, we don’t talk about an authorization/authentication in this tutorial, I guide to my article on Vertx JWT authentication for more insights.

An AppEndpoint structure

In a nutshell, the AppEndpoint class is an another Vertx’s AbstractVerticle, but with a difference – it also has a main() method. In main() method we would deploy both AppEndpoint and BikeService verticles.

In start() method of AppEndpoint we need to do three things:

  1. Initialize a HttpServer
  2. Assign routes
  3. Create Senders – when we receive a HTTP request, like GET, we dispatch its data, like bike_id and then via an EventBus send a request to the responsible Consumer method in BikeService.

Consider this code snippet with basic code for AppEndpoint:

public class AppEndpoint extends AbstractVerticle{

	private final int PORT = 4567;

	@Override
	public void start(){
		HttpServer server = vertx.createHttpServer();
        Router router = Router.router(vertx);
        server.requestHandler(router).listen(PORT, res->{
        	if (res.succeeded()){
        		System.out.println("Server is created! YEEEEAH!!");
        	} else {
        		System.out.println("Something went wrong: "+res.cause().getLocalizedMessage());
        	}
        });
	}

	public static void main(String[] ar){
		Vertx vertx = Vertx.vertx();
		vertx.deployVerticle(new AppEndpoint());
	}
}

Let go line by line to be sure what did we write here. Inside start() method we started with creating of HttpServer. This component is responsible for listening of incoming requests. Actually, servers are a part of Vertx core, but it offers a limited functionality.
In order to build a web application that serves different HTTP routes, we should use Router. A router takes an HTTP request and finds the first matching route for that request, and passes the request to that route. Router is tied with the concept of Handlers, that we would observe soon.
Then, we need to assign Router to a HttpServer‘s route with no matching criteria so it will match all requests that arrive on the server. We also provide the Port, that would be listened by our server.
Finally, inside main() method we initialise Vertx instance and deploy the AppEndpoint.

Handlers and what they do

The Route can have the Handler associated with it, which then receives the HTTP request. Then, we can do something with the request, and then, either end it or pass it to the another Handler. While the code above is perfectly working, it does nothing. Let add a Handler and serve a request:

	@Override
	public void start(){
		HttpServer server = vertx.createHttpServer();
        Router router = Router.router(vertx);

        //add this code!!!
        router.get("/hello").handler(context->context.end("Hello world, Vertx is up and running"));

        server.requestHandler(router).listen(PORT, res->{
        	if (res.succeeded()){
        		System.out.println("Server is created! YEEEEAH!!");
        	} else {
        		System.out.println("Something went wrong: "+res.cause().getLocalizedMessage());
        	}
        });
	}

As you could notice, we added the Handler that contains three elements:

  1. HTTP request verb (GET)
  2. Address (/hello)
  3. And handler itself

You can run your app and access localhost:4567/ in your favourite browser. That is what you should see:

You can notice, that a handler’s API looks similar to messaging API – and this is true. A handler itself is a lambda expression, that passes RoutingContext as an argument. Check official docs on HTTP routing for more details, as we would stop in details and would proceed towards AppEndpoint itself.

Putting it all together

Let put together concepts we learned so far with Vert.x routing. In this part we are working on AppEndpoint, and we should connect REST API with underlaying BikeService. Again, let start with searching for an individual bike (this code snippet represents a single route):

router.get("/bikes/:id").handler(context->{

	//get ID
	String bikeId = context.pathParam("id");
	//create request
	JsonObject payload = new JsonObject().put("bike_id", bikeId);
	//send message
	eventBus.send("bikes.one", payload, result->{
		if (res.succeeded()){
			context.response().setStatusCode(200).end(Json.encodePrettily(result.result().body()));
		} else {
			context.response().setStatusCode(404).end();
		}
	});
});

You remember, that when we worked on BikeService, we created Consumers. A consumer in Vertx gets some information (Message) from sender and then replies (optionally). In Sender code above we specify this message – payload as JSON and send it with EventBus. Than we provided a lambda expression, that deals with an answer from consumer.
Our bikes.one consumer in the BikeService receives an id of the specific bike, processes a DAO operation (in non-blocking manner) and then replies back with JSON-encoded data. Or it can fail, if nothing would found. In Sender we just assert a result of send operation and return to user either Bike or nothing.
This is mostly all for this topic, but before proceed to the last chapter, there a small thing you should know.

Send data to AppEndpoint

When we need to GET something from a server, it is straightforward – just register a route and handler for it. But when we need send something to server (as POST or PUT), we need to add an additional line of code – create a BodyHandler first and then register routes:

//1. Create a BodyHandler
router.route("/bikes/*").handler(BodyHandler.create());
//2. Then just add routes
router.post("/bikes").handler(context->{
	/*code goes here*/
});

The complete AppEndpoint source

So, we learned everything we need to finish with the AppEndpoint:

public class AppEndpoint extends AbstractVerticle{

	private final int PORT = 4567;

	@Override
	public void start(){
		HttpServer server = vertx.createHttpServer();
        Router router = Router.router(vertx);
        EventBus bus = vertx.eventBus();
        router.get("/bikes/:id").handler(context->{
			String bikeId = context.pathParam("id");
			JsonObject payload = new JsonObject().put("bike_id", bikeId);
			bus.send("bikes.one", payload, result->{
			if (res.succeeded()){
				context.response().setStatusCode(200).end(Json.encodePrettily(result.result().body()));
			} else {
				context.response().setStatusCode(404).end();
			}
		});
		router.route("/bikes/*").handler(BodyHandler.create());
		router.post("/bikes").handler(context->{
			JsonObject payload = context.getBodyAsJson();
			bus.send("bikes.add", payload, result->{
				c.response.setStatusCode(200).end(Json.encodePrettily(result.result().body()));
			});
		});
		router.put("/bikes").handler(context->{
			JsonObject payload = context.getBodyAsJson();
			bus.send("bikes.update", payload, result->{
				c.response.setStatusCode(200).end(Json.encodePrettily();
			});
		});
		router.delete("/bikes/:id").handler(context->{
			JsonObject payload = new JsonObject().put("bike_id", context.pathParam("id"));
			bus.send("bikes.delete", payload, result->{
				c.response.setStatusCode(200).end(Json.encodePrettily();
			});
		});

        server.requestHandler(router).listen(PORT, res->{
        	if (res.succeeded()){
        		System.out.println("Server was created");
        	} else {
        		System.out.println("Something went wrong: "+res.cause().getLocalizedMessage());
        	}
        });
	}

	public static void main(String[] ar){
		Vertx vertx = Vertx.vertx();
		vertx.deployVerticle(new AppEndpoint());
	}
}

Conclusion

In this part we finished with the AppEndpoint and now we have a complete microservice application. In the final chapter we would assemble everything together and deploy it to Heroku.

You can find a full source code for this tutorial on my github. Feel free to clone it:

git clone https://github.com/mednikovnet/vertx-microservices-tutorial