How to Write an Axon Application?
Axon is a popular framework for writing Java applications following DDD, event sourcing, and CQRS principles.
Join the DZone community and get the full member experience.Join For Free
Axon is a popular framework for writing Java applications following DDD, event sourcing, and CQRS principles. It is especially useful in a microservices context. Learning to write a proper Axon application from scratch consists of various aspects. There's the conceptual stuff to grasp and details about the actual framework — which dependencies to include and which annotations to use. After you've grasped the basics (perhaps doing the quick start), the reference page will guide you further. Another good place to start is the upcoming webinar.
However, talking with Axon customers, I found out that there's something we may need to be a little more clear about: what does the general structure of a real-life Axon application look like? What goes where and how do you prevent a mess? In this blog, I'll try to shed some light on this topic.
Domain Model and Adapters
First of all, Axon is heavily inspired by domain-driven design (DDD). Some DDD concepts are an essential part of the framework (aggregate, domain event, repository), whereas others are not explicitly part of the framework but should, nevertheless, be part of your thinking (anti-corruption layer, value object, entity, bounded context).
Classic texts on the subject are from Eric Evans and Vaughn Vernon. Both come highly recommended. Personally, I think Vaughn’s book is more practical and an easier read. It has loads of stuff that’s relevant to building an Axon application, and I would highly recommend studying it. Below, I’ll try to cover a few key DDD ideas that directly affect how you should structure an Axon application.
The core notion of DDD is, of course, domain. The domain is not a Java package or similar. It’s a sphere of activity in the real world, like for instance “pizza delivery." We assume that we’re building an application to support processes in such a domain. In realistic situations, we, as developers, won’t fully understand the domain up front, so a lot of communication with domain experts is necessary. In that way, we’ll end up with a shared domain model. Again, we do not code yet, but we’re getting closer. A core idea of DDD programming is that you construct the software in such a way that makes it part of a faithful implementation of the domain model. In complex domains, this will lead to software that is much more maintainable and reliable than software written using alternative approaches, like transaction scripts operating on data structures.
For an Axon application, this means the following: a clearly separated part of your source code (like a package or subproject) should be devoted to implementing your domain model and nothing else. Your domain model will also communicate with the outside world (for instance, it might receive HTTP requests and send out emails). The technicalities of these links should not be in the domain model, simply because they aren’t part of the domain model. You need to prevent your domain model code from being influenced by this, by putting the integration code in adapter classes outside of your domain model. These classes should not contain business logic (that belong in the domain model), but simply translate representations.
Domain Model Representation: API
Axon assumes an approach where interaction with the domain model takes place through explicit message objects. The messages belong to one of three stereotypes: commands, events, or queries. Adapters can send these messages to the domain model by publishing them on the corresponding bus or listen for them by having a handler registered on the bus. For commands and queries, messages are usually sent through the gateway, which is an easy-to-use facade on the bus.
This approach implies that the commands, events, and queries are the API to your domain model. There will be all kinds of other stuff in the domain model side, probably at the very least some aggregates. From an adapter perspective, that’s all implementation that's not directly accessible.
Importantly, between multiple (micro)services using Axon, communication can happen directly (using Axon Server or another distributed bus implementation). Adapters, like described previously, are required to make Axon-based domain model representations interact with the outside world, but they are entirely redundant to exchange commands, event, and queries between Axon-based components.
In any serious Axon application, you’ll have quite a lot of these commands, events, and queries. From a DDD perspective, these are value objects: immutable values, without any form of a lifecycle, for which equality is determined by the equality of their fields. Technically, we’re talking about many private final fields, an all-arg
constructor (possibly a
equals, and hashcode. Explicitly coding all of this in Java is hardly an option. Axon practitioners usually rely on Lombok (
@Value), Kotlin (data classes), or, sometimes, Scala (
caseclasses) to drastically reduce this boilerplate. Kotlin, in particular, may easily be mixed with Java if you want to use it for this purpose without adopting Kotlin for everything.
A thing to note about these value objects is that they will be shared between Axon components in a serialized form (using the serializer you configured). So whether or not you share the actual value object classes between separate but communicating Axon projects is a matter of choice.
Domain Model Representation: Implementation
At the domain model implementation side, you’re going to have components that react to incoming commands, events, and queries and/or produce them. A key example of this is an event-sourced aggregate, which may consist of one or more entities. Aggregates listen to commands and produce events.
Another type of component usually found in an Axon application is a projector (which is responsible for creating projections, also called read-models). It listens to both events and queries. In response to events, it will update its data store, so it can give accurate answers to queries after that. This is a core component of CQRS.
Several other components may exist as well.
Sagas respond to events, have state, and send out commands, managing transactions and business processes. There may be stand-alone handlers of any type (domain services), implementing business logic and possibly publishing new messages of any type.
Any code you have in this part of your software should be there because it represents the domain model. This is stuff that is totally recognizable to non-programming domain experts. If you feel the need to include a certain thing because it appears to necessary technically without being part of the domain model, re-think or put it somewhere else.
Taking into account what we said about the API and the implementation of the domain model, we’re now in a position to sketch a more detailed diagram of the overall structure of an Axon application:
Putting it All Together
Above, we’ve sketched out the various areas of a full Axon application but didn’t specify how to separate these areas. Axon doesn’t enforce anything here. It may be simply done through Java packages or even Java 9 modules. A little bit stricter, the various areas could be split into separate modules of a single application. And you may go further: with Axon, all exchange of commands, events, and queries is location-transparent, so the various objects that make up the domain model implementation, as well as the adapters, can run as separate microservices without code changes!
Because of the fact that you have all of this flexibility to split things up in the future, we recommend not to bother too much about this initially when creating an application. Simply choosing the appropriate package structure is a good starting point. Splitting up more strictly can be done as soon as it’s justified.
In addition to the areas already described, your concrete Axon application will also contain some Axon configuration code setting up the infrastructure. Axon, in particular, when used in conjunction with Spring Boot, has been designed to have very reasonable defaults to make initial development possible without doing any explicit configuration. Nevertheless, real-life applications usually have some specific configuration requirements. Common examples are setting up aggregate snapshots, aggregate caching, and specific serializers. Also, it’s quite common to configure interceptors to implement cross-cutting concerns, like authorization and logging. Axon’s busses offer an API to do this as part of your configuration code.
There’s one topic in this field that we didn’t touch upon yet: what if your Axon system covers multiple domains or bounded contexts? In that case, there will not be a single domain model API, but multiple sets of commands, events and queries. The key point here is that these various models shouldn’t pollute each other. Looking at this in detail is beyond the scope of this blog. Evans chapter 14 covers it extensively; one particular pattern to be aware about is the anti-corruption layer. Technically, Axon supports (but doesn’t require) explicitly using contexts to segregate messages belonging to separate contexts. Anti-corruption layer components would have access to 2 contexts, using 2 separate sets of Axon buses.
We hope the above is useful to Axon practitioners. Feel free to reach out to us with any questions or comments!
Published at DZone with permission of Frans van Buul. See the original article here.
Opinions expressed by DZone contributors are their own.