This will be the first in a series of posts discussing the technology I used to build the Altaridey platform.
Altaridey is a web-based platform for time series data analysis. It consists of several services having distinct functions, and a distributed database backing the services and allowing them to share data and application state, among other things. As the figure below illustrates, the “glue” that holds all the pieces together, apart from the database, is the web server. In this post, I will describe how I ended up choosing Vert.x for the web server technology.
The figures above and below should make it clear that the web server component of the application plays a unique role in the architecture. It is both a conventional web server that serves web requests arriving from the application users, and a client that communicates with the back end services in the course of servicing those web requests. As such, it is a kind of nerve center through which all data must pass on the way both from and to the user.
Of course, there is nothing especially unique about that mix of requirements. After all, you can think of any dynamically generated web page application (e.g. a search page that displays results from a database in response to some typed text) as being simultaneously a client that connects to the database server in order to retrieve the resultset and a server that serves those results to a client browser. What makes things a bit complicated in our case is the concept of data subscriptions: the need not just to service synchronously the original request arriving from the web browser, but also the associated asynchronous server-side events that arise over the course of the web session as data updates. The act of establishing a user session leads to the creation of a set of associated socket connections to the backend services, which carry real-time updates back to the web server, and on to the web client.
There are many modern applications that share this requirement of needing a server-side event to effect changes in the client browser window. Some examples are stock screens updating with the latest market prices; real-time chat or status applications, etc. All of these applications impose severe loads on legacy web infrastructure tailored to conventional HTTP requests. An HTTP request is meant to be processed synchronously, and quickly, so that just a few threads running inside a web server process can service 1000’s of requests per second. With server-side events, a web server must continually maintain a connection to the web client in order to forward the events to it. Because legacy web servers are designed to service only a few clients at a time, their capabilities are quickly overwhelmed by these persistently-connected modern clients.
The solution to this problem is a new type of web server architecture centered around an event loop. An event loop is a construction that associates events with their corresponding actions (or callbacks) and then launches an infinite loop that continually monitors for the occurrence of specific events–anything from changing descriptors that alert us to updates from the backend to asynchronous events triggered by other parts of the web server. When the loop encounters one of the registered events, it triggers the associated callback. Nearly every procedure in this new type of web server must be encapsulated in a non-thread-blocking callback, so it requires substantial reengineering of legacy code. The upside to these changes is that threads are no longer blocked waiting for events that might be few and far between; they can idle while the event loop stands sentinel and scans for the events, triggering the callbacks associated with the events as this becomes necessary.
So I knew I didn’t want Node for those reasons, as well as the difficulty of developing in a modular way under Node, but I liked a lot of things about its model and ecosystem. I also knew that I needed a framework that would be cross-platform, which left JVM-based solutions such as Play, Vert.x, Spring Boot, etc as the obvious choice. Among these, Vert.x is the lightest-weight framework, scales across available threads natively, and across multiple servers thanks to Hazelcast, and it does not commit you to a particular vision of the world (as Play does with the MVC pattern). It does take the “reactive” moniker to heart: like Node, Vert.x cannot abide anything that will block the event loop, so very little legacy code can survive unscathed. It was a minor consideration for me because I was starting a completely new project, but YMMV.
What really sold me on Vert.x is an elegant abstraction called the event bus. The event bus is a way to pass events around a vert.x application, and because it spans both the web server and the web client (by way of WebSockets and SockJS), it has a compelling use case when we need to pass server-side events up to the web client, and vice versa. That said, I don’t want to oversell it: it’s just a useful tool synthesized out of existing technologies, not something that is conceptually new or revolutionary.