Build a micro service application with Vertx: a complete tutorial (part 2/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

Step 0. Configure dependencies

First step of writing any application is to configure dependencies. In our example application, we would use Maven for it. Here is the list of external dependencies, you should include in your Pom.xml:

  • Vertx Core
  • Verx Web
  • MySQL Java Connector
  • JUnit 5
  • Mockito-Jupiter
  • Vertx Unit

Step 1. Create a BikeService.java

We start with a creation of the BikeService. This component would be responsible for basic CRUD operations, related to bike management in our application. NB!: different rights belong to different user roles, for example any user can search for available bikes, but only company employees can add, remove or update bike entities. This is known as role based access control. In our microservice architecture, we provide it inside AppEndpoint component.

As I mentioned in the previous part, Service is an independent unit of computation. In terms of Vertx, this is called verticle. So, we need to extend an AbstractVerticle and override start() method (this is a place, where Vertx magic happens). Also, don’t forget to create a constructor, that accepts IBikeDao argument:

public class BikeService extends AbstractVerticle{

	private IBikeDAO dao;
	
	public BikeService(IBikeDAO dao){
		this.dao = dao;
	}
	
	@Override
	public void start(){
	}
}

Step 2. Communication

In the point-to-point messaging pattern, Service receives requests from Endpoint and, in some situations, can provide an answer. Do you remember, we have talked about communication in microservices app? In our example, Service works as a consumer and Endpoint works as a sender.

Vertx has own methods to implement a point-to-point messaging. In Consumer part we need to:

  1. Obtain a Vertx.EventBus reference
  2. Set a Consumer with eventBus.consumer() method. Here we need to provide an address of consumer. NB!: it can be absolutely any string, in any case, I recommend you to stick to some naming conventions in your apps.
  3. Finally, we should register a Handler. Handler is a simple lambda expression, that accepts Message as an argument. Messages have a body, which can be null, and headers, which can be empty.
EventBus eventBus = vertx.eventBus();
eventBus.consumer("address")
	.handler(message->{
		//do something
});

But Message itself is a wrapper construction, or a unit of messaging, if you like. To access a payload we use body() method. As we agreed before, we use JSON to transfer messages between components. In order to access payload as JSON-encoded data, use this static handy method:

eventBus.consumer("address")
	.handler(message->{
      JsonObject payload = JsonObject.mapFrom(message.body());
});

Now we can use getter methods to access values of JSON payload.

What if we need to send information back to sender? We use reply() method. Message.reply() accepts Object as a payload, so we can pass data back to sender. NB!: We agreed to JSON to inter-component communication, so before sending a reply, don’t forget to encode data as JSON:

Data data = ...
message.reply(JsonObject.mapFrom(data));

A fin de cuentas, what if the DAO is unable to find a result? If you want to notify the sender that processing failed, then fail() can be called. It accepts two parameters:

  1. Int = error code
  2. String = message, that would be outputted with STDOUT

NB!: You can choose any values for these arguments. But I suggest you to use common HTTP response status codes as they are de-facto standard.

Step 3. DAO

In order to abstract data-access specific logic in a Data-Access-Object. DAO is an old design pattern, that is used in Java web applications.

Let create IBikeDao.java with following methods:

public interface IBikeDAO{
	
	List<Bike> findBikesNear (double lat, double lon);
	
	Bike findById (String bikeId);
	
	void add (Bike b);
	
	void remove (String bikeId);
	
	void update (Bike b);

}

NB!: If you are not familiar with this pattern, feel free to review this tutorial.

Step 4 Put it all together (but this is not the end)

Now we can implement all handlers:

public class BikeService extends AbstractVerticle{

	private IBikeDAO dao;

	public BikeService(IBikeDAO dao){
		this.dao = dao;
	}

	@Override
	public void start(){
		EventBus bus = vertx.eventBus();
		bus.consumer("bikes.one").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			String bikeId = payload.getString("bike_id");
			Bike bike = dao.findById(bikeId);
			if (bike!=null){
				messsage.reply(JsonObject.mapFrom(bike));
			} else {
				message.fail(404,"Nothing found!");
			}
		});
		bus.consumer("bikes.near").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			double lon = payload.getDouble("lon");
			double lat = payload.getDouble("lat");
			List<Bike> bikes = dao.findBikesNear(lat,lon);
			if (bikes!=null){
				messsage.reply(JsonObject.mapFrom(bikes));
			} else {
				message.fail(404,"Nothing found!");
			}			
		});
		bus.consumer("bikes.add").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			Bike bike = Json.decode(payload.toString(), Bike.class);
			String bikeId = UUID.randomUUID().toString();
			bike.setBikeId(bikeId);
			dao.add(bike);	
		});
		bus.consumer("bikes.remove").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			String bikeId = payload.getString("bike_id");
			dao.remove(bikeId);
		});
		bus.consumer("bikes.update").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			Bike bike = Json.decode(payload.toString(), Bike.class);
			dao.update(bike);
		});
	}
}

Step 5. And this is the final step

Well there is a perfect bottleneck in a code snippet above. And I don’t talk about using null to check an absent bike. It is a very bad design practice, you can examine a possibility of using Optional interface instead. Although, in my personal opinion, it is even worse.

Nevertheless, this is NOT a problem I’m talking about! Remember, I told you in the last chapter, that we would use MySQL as a data storage. JDBC API is a perfect blocking API. Do you remember the Golden Rule of Vertx?

Vertx Golden Rule – Don’t Block the Event Loop

Vertx offers us two possibilities to avoid blocking when we execute a blocking piece of code.

  1. Using Vertx async JDBC driver – this is a bad idea, as we want to abstract DB-specific code in DAO. However, in this case, we must follow Vertx DB-specific logic in the interface, that makes it highly coupled with the implementation.
  2. Using Vertx.executeBlocking() – this is what I recommend

Vertx.executeBlocking() specifying both the blocking code to execute and a result handler to be called back asynchronous when the blocking code has been executed.

vertx.executeBlocking(future->{
	/* here goes blocking code
	 * use future.complete(Object) to pass result*/
}, result ->{
	/*access data with result.result()*/
});

Now, we can refactor our BikeService and eliminate blocking code.

public class BikeService extends AbstractVerticle{

	private IBikeDAO dao;

	public BikeService(IBikeDAO dao){
		this.dao = dao;
	}

	@Override
	public void start(){
		EventBus bus = vertx.eventBus();
		bus.consumer("bikes.one").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			String bikeId = payload.getString("bike_id");
			vertx.executeBlocking(future->{
				Bike bike = dao.findById(bikeId);
				if (bike!=null){
					future.complete(JsonObject.mapFrom(bike));
				} else {
					future.fail("Nothing found");
				}
			}, result->{
				if (result.succeded()){
					message.reply(result.result());
				} else {
					message.fail(404,"Nothing found!");
				}
			});
		});
		bus.consumer("bikes.near").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			double lon = payload.getDouble("lon");
			double lat = payload.getDouble("lat");
			vertx.executeBlocking(future->{
				List<Bike> bikes = dao.findBikesNear(lat,lon);
				if (bikes!=null){
					future.complete(JsonObject.mapFrom(bikes));
				} else {
					future.fail("Nothing found");
				}
			}, result->{
				if (result.succeded()){
					message.reply(result.result());
				} else {
					message.fail(404,"Nothing found!");
				}
			});
		});
		bus.consumer("bikes.add").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			Bike bike = Json.decode(payload.toString(), Bike.class);
			String bikeId = UUID.randomUUID().toString();
			bike.setBikeId(bikeId);
			vertx.executeBlocking(future->dao.add(bike),
 result->System.out.println("Bike added"));
		});
		bus.consumer("bikes.remove").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			String bikeId = payload.getString("bike_id");
			vertx.executeBlocking(future->dao.remove(bikeId), 
result->System.out.println("Bike removed"));
		});
		bus.consumer("bikes.update").handler(message->{
			JsonObject payload = JsonObject.mapFrom(message.body());
			Bike bike = Json.decode(payload.toString(), Bike.class);
			vertx.executeBlocking(future->dao.update(bike), 
result->System.out.println("Bike update"));
		});
	}
}

Conclusion

In this part we talked about messaging and how to execute blocking code in Vertx in non-blocking manner. We would continue with database connection and testing in the next chapter.

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

git clone https://github.com/yuri-mednikov/bike-share