2
votes

I'm new to Axon Framework, CQRS and DDD. I was taught to create simple CRUD applications using relational databases. Hence, I am first focused on building the data model, not the domain model. I would like to change my approach to software to a more pragmatic and create real-world apps. Therefore, I want to use the CQRS pattern and Event Sourcing.

I'm working on a library app right now using Spring Boot and Axon Framework. One of the basic requirements is that the user has borrowed the book. I have two Aggregates for User and Book.

This is my BookAggregate:

    @Data
    @AllArgsConstructor @NoArgsConstructor
    @Aggregate
    public class BookAggregate {
    
        @AggregateIdentifier
        private UUID id;
        private String name;
        private String isbnNumber;
        private int amountOfCopies;
        private Author author;
        private Genre genre;
        private PublishingHouse publishingHouse;
        
        ...

And this is my UserAggregate:

    @Data
    @AllArgsConstructor @NoArgsConstructor
    @Aggregate
    public class UserAggregate {
    
        @AggregateIdentifier
        private UUID id;
        private String firstName;
        private String lastName;
        private String email;
        private String password;
        private String confirmPassword;
        private Set<Role> roles;
        private Date birthDate;
        private String telNumber;
        private Address address;
    
        ...

My question is: should I create an intermediary aggregate between these two aggregates something like SQL JOIN? In this one example, I'd like to see how similar use cases where two aggregates communicate with each other are implemented in Axon Framework.

2

2 Answers

5
votes

should I create an intermediary aggregate between these two aggregates something like SQL JOIN? In this one example, I'd like to see how similar use cases where two aggregates communicate with each other are implemented in Axon Framework.

Great question. I'm going to start by saying no, and then try to help you understand why.

Let's start with why aggregates exist. From the DDD reference:

Aggregates

It is difficult to guarantee the consistency of changes to objects in a model with complex associations.
[...]

Therefore: Cluster the entities and value objects into aggregates and define boundaries around each. Choose one entity to be the root of each aggregate, and allow external objects to hold references to the root only

OK, so the reason for using aggregates is to avoid intermediaries.

Since you are doing CQRS (which implies DDD and event sourcing), you have a command side and a query side (Command Query Request Segregation). If you are more familiar with CRUD style applications and relational databases, chances are that you're focusing on the query side and not the command side.

Let's clarify a few things, in CQRS:

  • Aggregates are only relevant to the command side
  • Aggregates are responsible for handling commands and publishing events
  • Aggregates are responsible for enforcing business rules

What this means is that for the command side: Commands are passed to aggregates, which decide if they should publish events or deny the command.

On the query side: Events originating from the command side are processed to build up a query model (probably what you refer to as the data model)

Hence, I am first focused on building the data model, not the domain model

In an event sourced system, you typically focus on the domain model and the associated events. The query model can be fully built based on the events from the domain model.

Since you are not yet focusing on the domain model you don't yet have to care about aggregates :)

Again, back to your original question:

One of the basic requirements is that the user has borrowed the book

should I create an intermediary aggregate between these two aggregates something like SQL JOIN?

Based on your domain, you already have a User and Book aggregate. You have single requirement that users can borrow books. Now I'm no expert on your domain, but ask yourself: why do you need another aggregate? What would this new aggregate be responsible for? What would be wrong with just having Book.borrow(userId) which publishes a BookBorrowed(bookId, userId) event if the user is allowed to borrow the book?

3
votes

I very, very much like the suggestions given by @martingreber earlier. Please take those into account, as those are about the conceptual necessity of your current set-up.

From a pragmatic approach, there are also a couple of things I'd like to add (although I wouldn't anticipate these to form the answer completely).

Firstly, your BookAggregate and UserAggregate contain quite some state. When doing CQRS, I would recommend that your command model (read: the aggregates) only contain state necessary for task execution. What I mean with that, is that the sole fields required to reside inside the BookAggregate and UserAggregate are those you use to perform validations with inside your @CommandHandler annotated methods. The rest can all go, as it is no being used. Furthermore, if it turns out to be required at a later stage, you can simply add another event sourcing handler later. That's the beauty of event sourcing really; you can add state from events which are already present in the aggregate's event stream whenever you really start needing it.

Secondly, towards the inter-aggregate communication. It is good to note, as was also pointed out by Martin, that Aggregates typically are the Command Model. As such, they only handle Commands and publish Events. The @EventSourcingHandler annotated methods might suggest you can handle any event, but in reality, the Aggregate will only handle the events it itself has published. This holds as it is those events which you would need to recreate the state of the aggregate.

Now, it is the events which will allow for the inter-aggregate communication you require. As the aggregates just work with their own concepts and state, you will have to introduce another component which reacts to the published events to dispatch commands to other aggregates. This component can simply be a regular Event Handling Component (read: a class with @EventHandler annotated functions in it). If the process you want to react to has a notion of times, requires state to perform its task and communicates with N aggregates? Then you could use a Saga for example.

Thirdly, and arguable most important, is to decide whether you really need to have two distinct aggregates in your system. It is this why I applaud @martingerber his answer.

Nonetheless, these are my two cents. Hope it helps you out.