Building Reactive Systems With Axon Framework
In this article, we look at the Axon framework, a Java-based, open source framework for application development. Read to learn about Axon!
Join the DZone community and get the full member experience.Join For Free
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 blocked certain operations because programming models used back then didn’t allow many asynchronous things to happen in code. Unfortunately, these systems aren’t as responsive as we expect them to be, they cannot be scaled that easily, and they use processor time waiting for operations to complete when they could do something else (processing another request, doing some background calculations, etc.).
The Reactive Manifesto was created in order to define the properties of a system that's able to respond to the aforementioned challenges. Systems that are reactive are Message Driven, Responsive, Resilient, and Elastic (Image 1). In this blog post, I’ll focus on how the Axon Framework helps us build reactive systems.
Image 1 (source: https://www.reactivemanifesto.org/)
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 behavior is useful when it comes to 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 as soon as the command is dispatched, control is returned to the dispatching thread. However, error handling must be done using a callback mechanism.
Disruptor Command Bus - This implementation is also asynchronous and it uses disruptor (High-Performance Inter-Thread Messaging Library) in order to achieve high-performance characteristics. There are some disadvantages when it comes to error handling which exceeds the purpose of this blog post.
Distributed Command Bus - If we want to send commands to different JVMs this is the implementation of Command Bus to 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, there are certain facts that happened in the system which 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 which transports query messages - messages that represent a request for information. Currently, it supports three types of queries:
Direct query - there are several query handling components which 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 requests and give you all the responses, allowing you to reduce those responses to 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 to stay up-to-date as the response of a query changes. To achieve reactiveness, project reactor types are used to represent the response to the subscription query - initial response is a mono and incremental updates to the query are packaged in a flux.
Axon Framework Is Responsive
Responsiveness refers to the specific ability of a system or functional unit to complete assigned tasks within a given time (according to Wikipedia). Tasks are something that expresses intention, or, if we translate it into Axon vocabulary, they are commands. So, we want our commands to execute in a timely manner.
Commands dispatched to a command bus can be processed in a synchronous or asynchronous manner (as explained in the previous chapter). When it comes to synchronous processing, we can block indefinitely (which is not really recommended if we want to be responsive) or 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.” This 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 in spite of the failure. To ensure this, when the Axon Framework starts message processing it starts a Unit of Work which is tied to a transaction. As a programmer, you can provide your own Error Handlers which will be invoked on a failure of message processing. 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). The requirements of more and more systems are exceeding the capabilities of a single machine - in other words, cannot be scaled vertically anymore. 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 on 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 which if used properly help us build reactive systems and improve end-user experience of our applications. All these components can be spread across several nodes in order 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 at the end more important to our customers.
Published at DZone with permission of Milan Savic. See the original article here.
Opinions expressed by DZone contributors are their own.