Building Reactive Systems with Axon Framework
In today’s software world, everything’s changed when it comes to operational (non-functional) requirements. We have applications running on thousands of cores, producing petabytes of data, and, of course, users expect to have a response time under 100ms. Several years ago, we used to build systems that block certain operations because programming models used back then didn’t allow many asynchronous things to happen in the code. Unfortunately, these systems aren’t as responsive as we expect them to be. They cannot be scaled that easily. Instead, they use processor time, waiting for operations to complete when they could do something else (processing another request, doing some background calculations, etc.).
Axon Framework is Message Driven
The most important notion in the Axon Framework is the MESSAGE. There are three types of messages that the framework introduces:
- Command - represents the intent to perform an action
- Event - represents a notification that something (important) has happened
- Query - represents a request for information
With this kind of approach, your APIs are messages which your system uses to communicate internally and externally. The framework provides infrastructure components for dispatching these messages (Command Bus, Event Bus, and Query Bus).
A Command Bus is used to dispatch command messages to the aggregates that handle them. There are several implementations of Command Bus:
- Simple Command Bus - Processes the command on the thread that dispatched it, which causes the dispatching thread to block until the command is processed. This behaviour is useful for error handling since exceptions caused by command processing can be handled on the dispatching thread.
- Asynchronous Command Bus - The command is processed on a different thread from the one that dispatched it. The positive thing here is that control is returned to the dispatching thread as soon as the command is dispatched. However, error handling must be done using a callback mechanism.
- Disruptor Command Bus - This implementation is also asynchronous, and it uses a disruptor (High-Performance Inter-Thread Messaging Library) to achieve high-performance characteristics. There are some disadvantages to error handling, which exceeds the purpose of this blog post.
- Distributed Command Bus - If we want to send commands to different JVMs, the Command Bus implementation will be used. It is aware of multiple instances of Command Bus working together to spread the load.
When a command is processed in event-driven applications, certain facts in the system are represented by event messages. Those messages are published on an Event Bus. Later on, these events are used to build query models in your CQRS applications. In short, CQRS is an approach where concerns of command processing and query model updating are separated.
The Query Bus is a component that transports query messages - messages that represent a request for information. Currently, it supports three types of queries:
- Direct query - several query handling components can respond to your request for information, and one of them will be invoked by the Query Bus. The response to the query is the response given by that component.
- Scatter-gather query - The Query Bus will invoke all query handling components which can respond to query request and give you all responses, allowing you to reduce those responses into the response you’d want to use.
- Subscription query - This type of query allows a client to get the initial state of the model it wants to query and stay up-to-date as the response of a query changes. To achieve reactiveness, project reactor types represent the response to the subscription query - the initial response is mono, and incremental updates to the query are packaged in a flux.
Axon Framework is Responsive
Responsiveness refers to the specific ability or functional unit to complete assigned tasks within a given time (according to Wikipedia). Tasks express intention, or if we translate it into Axon vocabulary, they are commands. So, we want our commands to execute promptly.
Commands dispatched to a command bus can be processed synchronously or asynchronously (as explained in the previous chapter). We can block indefinitely when it comes to synchronous processing (which is not really recommended if we want to be responsive). We can block for a (maximum) certain amount of time and wait for the command to be processed.
With asynchronous processing, control is returned to the caller just after the command is dispatched, and provided callback is invoked when processing is done. From a user perspective, this is better since we can immediately respond with something like: “Your order is accepted. We will inform you when it is processed.” which is much better than waiting for several seconds (minutes sometimes) to see the result of an action.
Axon Framework is Resilient
Resilient systems are systems that handle failures well and can easily recover from them. In other words, if one feature fails due to some network failures, third-party system unavailability, programmer error, etc. We want our system to keep on working despite the failure. To ensure this, when the Axon Framework starts message processing, it starts a Unit of Work tied to a transaction. As a programmer, you can provide your own Error Handlers invoked on message processing failure. So, if something goes wrong, the transaction is rolled back and provided Error Handlers are invoked. Having this, our failure is isolated on a message level and prevents the whole system from crashing.
Axon Framework is Elastic
When the number of requests that our system needs to handle starts rising, we can scale it vertically (increase the capacity of a single machine - more CPU power, more operational memory, etc.) or horizontally (add more machines to spread the load). However, the requirements of more and more systems are exceeding the capabilities of a single machine - in other words, it cannot be scaled vertically anymore. So, inevitably, we need to run our code on several machines.
Using the Distributed Command Bus, we can send commands to different nodes (JVMs) and spread the load that way. Event processing can be done asynchronously and on different nodes. One important thing to underline here is location transparency - components aren’t aware, not interested in their relative locations. It’s only a matter of configuration whether your system runs on a single node or several nodes (distributed). Your code does not have to change at all. Location transparency makes scaling (up or down) horizontally really easy when the load on your system starts to vary.
Axon Framework provides infrastructural components that help us build reactive systems and improve the end-user experience of our applications if used properly. All these components can be spread across several nodes to balance the load, and each component can be customized to specific needs. Having Axon Framework in our toolbox makes us more focused on the business domain, which is, in the end, more important to our customers.
Milan Savić is Software Engineer at AxonIQ. Milan has experience with various software projects ranging from chemical analyzers to contactless mobile payment systems. In some of those projects, CQRS and Event Sourcing came as a natural solution, but things had to be built from scratch almost every time.
Finding out about Axon Framework got him interested in being a part of the solution. In March 2018 he joined the AxonIQ team on a mission to build tools that will help others in building event-driven, reactive systems.