From Model to Code - Translating the (Event) Model into Code
In the previous blog post, we (well, mostly Sara Torrey) have discussed Event modeling and how it can be used as a tool to create a blueprint for your solution.
In this part, we will focus on translating the event model to the source code, by using Axon Framework as a programming model and Axon Server as an event store and message broker.
Messages
We have identified three types of messages: commands, events, and queries. It is natural to create a Java/Kotlin class for every message. For example, AddLessonCommand
or LessonAddedEvent
. Messages should be immutable. If you use the Kotlin programming language you can consider "data" classes. If you are using the Java programming language you can consider "records" (Java 14), or simple "pojos" with final attributes and only getters exposed. These messages are forming the core API of your system and they should be public.
The command side/model - Aggregates
Once you have your messages modeled in source code, you have to start thinking about which components are going to handle/publish these messages. These components are Aggregates.
An Aggregate is an entity or group of entities that are always kept in a consistent state (within a single ACID transaction). The Aggregate Root is the entity within the aggregate that is responsible for maintaining this consistent state. This makes the aggregate a prime building block for implementing a domain (command) model in Axon-based applications.
In this example, an aggregate root (Lesson) is a regular Java class that is able to handle specific command AddLessonCommand
and produce LessonAdded
event as a result. This logic is located in the specific method of the "Lesson aggregate root" class, which you would annotate as a @CommandHandler
. This is the place where all decisions and validations are made!
@Aggregate
class com.demo.music.command.Lesson {
// Some private attributes
// Constructor(s)
@CommandHandler
void on(com.demo.music.command.api.AddLessonCommand command) {
// Some validation, if needed.
apply(
new com.demo.music.command.api.LessonAddedEvent(
command.targetAggregateIdentifier(),
command.title(),
command.description()
)
);
}
The structure
You have already noticed that "Lesson" aggregate root class belongs to com.demo.music.command package, and that AddLessonCommand
and LessonAddedEvent
belongs to com.demo.music.command.api (sub)package.
com.demo.music.command.Lesson
com.demo.music.command.api.AddLessonCommand
com.demo.music.command.api.LessonAddedEvent
The command package represents the "command" side/model of the CQRS pattern, which is the dominant architectural pattern in our design.
What about the “query” side/model? What about green sticky notes? These belong to the com.demo.music.query package:
com.demo.music.query.LessonAvailabilityHandler
com.demo.music.query.api.FindAllAvailableLessonsQuery
com.demo.music.query.api.FindAvailableLessonByIdQuery
com.demo.music.query.PaymentsToProcessHandler
com.demo.music.query.api.FindAllPaymetsToProcessQuery
The query side/model
So far, you have only the "command" software model in place. The events that are published are stored somewhere (we are not going to discuss where exactly, yet). These events represent the fact, something that already happened, and they are not optimized for direct querying. You want to subscribe to these events on the "query" side, and to create denormalized views that are more adequate for querying.
The LessonAvailability
component is responsible for a few things:
- Subscribes to events that are published from the command side aggregates
- Projects the data from a handled event into some repository (SQL, NoSQL) in the format that is the most optimal for querying
- Exposes the query handlers that are able to handle Queries and respond with an appropriate response to the caller (UI). The query handlers are using repositories (managed in step 2) to retrieve the data.
The LessonAvailability
component can be modeled as a Java class:
@Component
@ProcessingGroup("LessonAvailabilityHandler")
class com.demo.music.query.LessonAvailabilityHandler {
// private attributes omitted: lessonAvailabilityRepository
// 1.
@EventHandler
void on(com.demo.music.query.api.LessonAddedEvent event) {
// 2.
var record = lessonAvailabilityRepository.save(new LessonAvailabilityEntity(event.getAggregateIdentifier().getIdentifier(), …)
. . .
}
// 3.
@QueryHandler
List on(FindAllAvailableLessonsQuery query) {
return lessonAvailabilityRepository.findAll().stream()
.map(this::convert)
.collect(Collectors.toList());
}
}
It is important to note that LessonAvailabilityHandler
component is reused two times (on two steps) on the blueprint flow image (image 1). There are two other “view” components that should be implemented identically: PaymentsToProcessHandler
and ReportsHandler
. This is how you utilize the CQRS pattern effectively, by allowing each step in the blueprint flow to have its own view. This will make steps in the flow more independent, loosely coupled, and more focused on the UX (user experience) as you will be serving specific data/view that this UI/UX step really requires.
The specification by example
As we gain a deeper understanding of how to reflect the event model into the software components, it is becoming more obvious that we are bringing "specification by example" to the software design/architecture level.
The specifications are made collaboratively with all participants. A Give-When-Then or Given-Then can be constructed one after the other very rapidly while being reviewed by multiple role representatives.
By using Axon Framework test fixture library, the transition to the source code (unit) tests is immediate:
These tests are unit and acceptance tests at the same time. One would prefer to have these tests written at first and then implement aggregate(s), practicing "test-driven development".
Security and Authorization
It is obvious from the blueprint (image 1) itself that specific roles (for example, a manager) are limited in command messages they can send or query messages they can issue. The blueprint also shows exactly where and when sensitive data crosses boundaries. This is very valuable and very responsible. Just follow the 'arrows', it is very transparent.
Ports and Adapters
Our Driving Adapters are mostly Controllers (Thymeleaf/MVC, REST, WebSockets) who are injected in their constructor with the concrete implementation of the interface (port) from the core domain. These interfaces (ports) and their implementations are provided by Axon platform out of the box:
- command bus (command gateway as a convenient facade)
- query bus (query gateway as a convenient facade)
Adapters are adapting the HTTP and/or WebSocket interfaces to the domain interfaces (ports) by converting HTTP requests to messaging API (commands, queries) and publishing them on the bus.
Our Driven Adapters are implementations of domain interfaces (ports) that are responsible for persisting (e.g event sourced aggregates) and handling events. Event handlers are creating read-only projections that are persisted in repositories. These interfaces (ports) and their implementations are provided by the Axon platform out of the box:
- Event sourcing repository
- Event bus
Axon Server implements all three types of buses: AxonServerCommandBus, AxonServerEventStore, and AxonServerQueryBus.
This way, Axon takes "location transparency" further than placing services behind a logical URL. In Axon, a component (for example a REST controller/adapter) that sends a message via CommandGateway/QueryGateway does not need to specify a destination for that message. Messages are routed via Axon Server based on their stereotype (Command, Query, or Event) and the type of payload that they carry. This will enable multiple deployment strategies, as now you can deploy REST controllers/adapters, command side, and query side components independently, as separate services if you like.
Axon Server is an event store as well. It is storing the events/facts durably, enabling event sourcing and preserving the history, which is valuable for auditing and regulatory purposes.
Closing thoughts
In this blog post, we focused on 'components': command side aggregates, query side event/query handlers, and adapters. We have visualized how these components communicate to each other by sharing messages via axon buses, enabling many different deployment strategies.
In the next blog post, we will zoom out, and visualize how multiple software systems fit together within the bounds of an enterprise. You will see how Bounded Context and Ubiquitous Language (DDD) patterns play an important role on a strategic level.
Until then… happy coding!