Reputation: 21
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.
Upvotes: 2
Views: 1482
Reputation: 610
As @martingreber writes:
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)
But how would you conceptually and programmatically in Axon communicate the occurrence of business relevant facts to other contexts (i.e. Aggregates) ? To me, the domain event is the instrument just for that.
If I have to use Commands
, I need to know who to ask and to ask for what exactly. This IMHO is tight coupling between the contexts. One business context cannot know what another business context is supposed to do or what not, especially in the future, when business constraints in different contexts have evolved independent from each other. That's pretty much why there are different contexts. Therefore, Command
seems to be the wrong concept, as according to Axon it is an expression of intent to do something in an aggregate (the exact wording of the definition escapes me at the moment).
How do I solve this problem in Axon ? Axon documentation, although otherwise great, is a bit confusing about this aspect. In Axon Server product page, it says
Axon Server has knowledge about the different types of messages that are being exchanged: events, sent from one service to one or many other services, to notify the services that something has happened, commands, sent to one service to do something
but in various other places it clearly says that an Aggregate will never receive Events from other Aggregates.
I guess, the obvious workaround maybe would be to degenerate
Commands
intoEvents
: ACustomer
aggregate processes aOrderCanceled
command it received from somewhere (lets say, a UI Controller), it issues an eventOrderCanceled
to itself and its query side, and it issues a command calledOrderCanceled
to anybody else interested ?
Is it just my OCD that finds that unclean ? :-)
Upvotes: 0
Reputation: 636
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:
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?
Upvotes: 5
Reputation: 7275
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.
Upvotes: 4