Nov
24
2020

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. 

Last image from the first blog post in this series: A board represents different events, commands and data views, organised and linked between them. The orange sticky notes representing the events are organised in three horizontal swimlanes, corresponding to different concepts/agregates: Account Registered is on the Account swimlane, Payment Requested, Payment Rejected and Payment Accepted are on the Payments swimlane; and Lesson Added, Lesson Request Rejected and Lesson Booked are on the Lesson swimlane. Also, the different screens and UI from the App are shown on the top and linked to the different Commands that are triggered from them.
 

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.

We start with the UI corresponding to the form to add a lesson. In this image we focus on the command side: The blue sticky note with the Add Lesson command, is classified to the "command side" area. The Add Lesson is linked to the Lesson Context (represented by a yellow sticky note), which representes the aggregate root, and generates a Lesson Added (orange sticky note) event that is sent to the query side.

 

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. 

Now we focus on the query side. The Lesson Added orange sticky note (the event sent by the command side in the previous diagram) is linked to the Lesson Availability green sticky note (corresponding to a view or a projection) on the query side. The Lesson Availability view will be used when the query side receives a findAvailableLessonByIdQuery (represented in a dark-green sticky note) sent by the RestController linked to the paymentDetails screen in the UI.
 

The LessonAvailability component is responsible for a few things:

  1. Subscribes to events that are published from the command side aggregates
  2. Projects the data from a handled event into some repository (SQL, NoSQL) in the format that is the most optimal for querying
  3. 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<LessonAvailabilityModel> 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. 

the sticky notes can be classified by contexts, and in different columns representing different scenarios to test. Each of these columns will arrange the sticky notes in three rows corresponding to the phases of the a test: "Given" (usually orange sticky notes corresponding to events), "when" (blue sticky notes corresponding to commands, or other orange sticky notes corresponding to events) and "Then" with the events (orange sticky notes) that should have been triggered or updates on the views/projections (green sticky notes) that should have been performed
 

By using Axon Framework test fixture library, the transition to the source code (unit) tests is immediate:

An example of a Test with the corresponding sticky notes as placed in the previous board for the given, when then stages of the test.
 

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
  • Full picture of how the screens from the UI, the Commands, Aggregates, Events, views and queries are identified: The UI to add a Lesson uses a RestController (identified here as a Command Side adapter), that receives the http request from the UI, and sends the "Add Lesson" command to the command side. On the right side, the UI with the payment details screen sends an http request to a Rest Controller (identified as the query side adapter) that will create and send a findAvailabitityByIdQuery to the query side.

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. 

The axon Server is represented in this image as the component that receives Commands and Queries from the Command and Query side adapters, and delivers those commands and queries to the right components. Also the events from Command side are sent to the Axon Server, that delivers them to the Query side that needs them.
 

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! 


Written by:
Ivan Dugalic

Ivan Dugalic

Solutions architect with significant experience in designing full stack application components and providing guidance to the solutions teams in development and implementation.

Skilled in a wide variety of technology stacks and learning quickly new technologies as needed. Experience covers all facets of design patterns, software architecture, continuous delivery, agile methodologies and best practices in constructing solutions that remain scalable, adaptable and replicable. Strong engineering professional with the Master of Science (MSc) focused on Computer Science from the University of Belgrade, Faculty of Mathematics.