Hello! I think that it is useful to publish this small case study that sums up my experience working with Vert.x. During this time, I worked with various projects from small-scale to big distributed microservices architecture. I learned a lot an importance of clean architecture in backend development, although Vert.x is a bit a dark horse of Java frameworks. While Spring and Co. have well-defined patterns that programmers should follow, Vert.x does not force you in one particular direction. Yes, this may seem as an advantage during initial stages, but later on you would find yourself in totally spaghetti code.
I hope my case study would help you to organize your Vertx-based projects. I summarized my experience in 4 patterns you can use to build web apps with this wonderful tool.
I assume that readers are familiar with core components like verticles, eventbus, writing non-blocking code.
Approach 1. MVC application
MVC (stands for Model-View-Controller) is a classical web (but not limited to) application pattern. It is with us for a while and it is not a Java trademark. Opposite, there are many MVC frameworks in C#, PHP, Python, JS etc. realms. You can build MVC-styled app with Vertx too. This pattern helps developers to decouple data access and business logic from the presentation (e.g. how it is presented to user). From a technical point of view, MVC is usually associated with server-side rendering. That means that web views are pre-rendered on a server, not dynamically generated by client side. There is a discussion client side vs. server side rendering, and I want to leave it aside, as it is not a topic of this post. Better, let explore how it is done with Vert.x. Please take a look on the graph below:
You can note, that I have 3 verticles on the scheme – one for API, one for database access, one for view rendering. Attentive readers may found it is similar to one described by Vertx creators in official manual. I don’t pretend to be an inventor of it, rather I would like to add my two cents as I think this is highly usable architecture for small projects.
Both DB verticle and view verticle are considered as worker verticles, that means they are executed outside of main thread, inside worker threads to avoid blocking. I suggest you to place them outside main thread, even if you use Vertx libraries. At any case they are very, very consuming and they are lazy by nature, e.g. you don’t need DB to always running, compare to API verticle. To sum up, I want to consider following points here:
- Place DB access and rendering code inside worker verticles to avoid blocking main thread
- Communicate between verticles using native eventbus messaging
- If it possible, stick to Vertx native clients
Approach 2. REST API (separation by layer)
For bigger projects we usually separate client-side logic from backend and connect them using REST API. In this case, Vertx does not handle view rendering, but only backend tasks. But we still can utilize similar structure, why not? I want to show you following graph:
Again, you could note that we separate verticles by layer: we have different components for API server, for database access, for providers. By providers I understand 3-rd party API, for example CRM or payments. This architecture was first, I used to build REST API with Vertx and it still seems to me very fair and simple:
- API verticle is responsible for handling HTTP REST API calls and authentication
- Database verticle handles data storage access
- Provider verticle is a component that connects your app with other cloud API, like CRMs, payment providers or weather API
Both database and provider verticles should also be workers as they consits of code that easily blocks main thread. Verticles are connected with eventbus messaging between each other.
Approach 3. REST API (separation by domain)
When you API grows bigger, it may be wise to separate components not by layer, but by domain. This corresponds to domain-driven design, which usually means that parts are defined by some subdomain. For example, we can have an e-commerce platform that have components like inventory service, order service, user service etc. What is crucial that each component has bounded context. With Vertx you can use an approach that I call Endpoint-Service-Repository. Take a look on the graph:
What you can note from the picture? Perhaps, following points:
- Each component has its own data access object (repository) that is usually placed inside component’s verticle, not in a separate one (you can perform it by using non-blocking code techniques)
- Components are connected to one endpoint, and are not connected with each other.
In this architecture communication with an outer world is performed via API gateway or endpoint, as you like to call it. It exposes REST API that external clients consumes. Verticles usually don’t connect with each other, as messaging with the endpoint is operated via same communication patterns, like in previous cases. This is a very good idea to organize your code in this manner, as this is almost microservices architecture. If you need to do something bigger, take a look on the next candidate.
Approach 4. Cluster
In the previous patterns we placed verticles inside a single application. But for big apps it is not a solution. Here you can use this approach:
Yes, it is based on all three previous cases. We have separate services, each of them consists of two+ verticles:
- Main verticle for API
- Worker verticle(s) for database access/external providers
Between verticles we use eventbus to communicate. Inside cluster, e.g. between each other we use messaging queues. STOP! why not Eventbus bridges? Yes, this may seem fair if you have services written with different technologies, to extend eventbus by using TCP or SockJS bridges and communicate with non-Vertx services. Although, it seems highly coupled with concrete framework, does not it? I think yes. What is better is to do these things:
- Use general messaging protocols like AQMP with RabbitMQ or Active MQ to ensure that non-vertx services can communicate with vertx services
- Organize each vertx service from two and more verticles. Main verticle is responsible for handling REST API; worker verticles are used to do database access or third-party provider API calls
- Optionally, use a single API gateway to communicate between cluster and outer world
You can note, that approaches are classified from small app to big microservice clusters. This is true, but don’t think that they are solid and unchangeable. What you should take from this post, is that you need to organize your code in a manner that permits you to write reactive applications. For this I advise you to use more than one verticle and separate really consuming operations, like data access, from main threads.
This case study sums up my experience of design and development of reactive backends with Vertx. This framework does not force you to follow limited number of paradigms, but this is paralyzing from another side. I organized my approaches into 4 patterns, that range from small MVC app to big microservice clusters. But what is more important – design your projects around reactive principles first. If you have notes or questions, don’t be shy to drop me a line or leave comments below.
Have a nice day!