Axon Aggregate + Saga behavior for common shop/library example

Hi everyone,
I am learning Axon since it looks like the best tool for ES now. I looked over existing examples on the internet and most of them look fine but I haven’t found one that suits my use-case.
I would like to create simple test software for handling library actions (most conferences focus on something like shop cart which is quite similar) but I think I have some troubles understanding that correctly.
I start with creating Book aggregate. We can safely assume that the name of the book is somewhat unique so let’s make it an AggregateIdentifier. I think we also might need fields like quantity, rented, reserved. I also see basic commands like AddBookCommand, ReserveBookCommand, RentBookCommand and proper events reacting to that commands. I also have Order aggregate with some unique ID in form of UUID let’s say. Now it gets tricky. It is not obvious for me which commands/event should be part of Rent process. I assume that user clicks books on some list (creates order and reserve books), and them after proceeding to some form of checkout he confirms and boom! books are rented. Also, multiple Orders might react to same Product events (Reserved, Rented) so it looks like I’d need to somehow map them to another format ReserveBookEvent(bookId) -> ReserveBookEvent(orderId, bookId) or just for ReserveBookCommand in Order aggregate use sendAndAwait to then react to the result in the same aggregate. So to generify this problem we have the situation where commandHandler has to send a command to another aggregate and react to this. Also, where is saga here? It sounds like a process: reserve->confirm/rent->return.
It is quite confusing for me, especially as for a beginner so every advice will do!
Thanks!

Hi Jędrzej,

In the scenario you’re sketching, I do think a Saga would be the most logical thing to have.
I will very likely not be able to fill all the holes with my explanation, but I hope it gets you up to speed at least.

My assumption is that, when you’re placing an Order to rent books, that that command, for example the ReserveBooksCommand, is published.
This command then (to me) sounds like the creation point of your Order aggregate to keep track of whom has rented out those books and for how long.
That command would thus contain things like the user placing the order and the books he/she/it is trying to reserve.

The Order aggregate would likely publish an event stating that a book reservation has been place, something like BookReservationPlacedEvent.

This BookReservationPlacedEvent in turn would be the starting point to instantiate the Saga which will coordinate the actions to actual reserve singular Book aggregates.
Thus, the Saga Event Handler for BookReservationPlacedEvent would publish 1…N commands, depending on the number of books being reserved, towards each Book aggregate to try to reserve it.
Now at this point you’ll have to think what happens if the Book cannot be reserved, for example because there are no books left to reserve to begin with.
This is something your Saga should cope with, by sending some form of compensating action to notify that a given Book couldn’t be reserved.

So, the mapping concern you’ve having, would initially live in the ReserveBooksCommand and BookReservationPlacedEvent I am guessing.
The command contains all the books you’re reserving, and the event contains both the Book IDs and the Order ID.

Lastly I’d like to suggest to not use the Book name as the Aggregate Identifier to a Book aggregate.
Although I catch you’re drift, in the Library domain the Book name is not the unique identifier for a book.

I think a UUID, or maybe the Book’s ISBN would be a better fit.

Hope this helps you out Jędrzej!

Cheers,
Steven

Hi @Steven,
I started with something that reminds of what you stated.

Book {
id : string;
quantity : number;
reserved: number;
rented: number;

ReserveCommand (check if quantity>0) -> ReserveEvent (quantity—;reserved++)
RentCommand (check if reserved > 0) -> RentCommand(reserved—;rented++)

and so on…
}

Then in Order I would have: {
AddToRentalCommand(bookID) {
commandGateway.sendAndAwait(ReserveCommand()) // some try catch probably
apply(new AddedToRentalEvent(bookID))
}
}

But then, it is not Saga which handles communication with Book. What do you think about that? Should it be moved to Saga? What the process of renting itself? Should Saga handle that as well?

W dniu czwartek, 13 grudnia 2018 22:05:18 UTC+1 użytkownik Jędrzej Frankowski napisał:

Hi Jędrzej,

I am assuming that the ‘Order’ you’re referring to is an Aggregate as well, right?
Assuming it is, I do have a couple of pointers I think you should be taking into account prior to doing this.

Axon’s (Aggregate) Repository implementations will place a lock on an Aggregate instance when it is being loaded prior to providing a given command message to them.
Thus, the ‘AddToRentalCommand’, which is handled by the Order Aggregate, will place a lock on that Aggregate.
As you’re however publishing another command from it’s command handler, you’re effectively locking a Book Aggregate too.
You’re thus linking your aggregates together in a way which in a bigger, more expanded system could very likely spell disaster.

To uncouple the aggregates from one another, an Event Handler which listens to a given event from the Order and publishes a command to the Book Aggregate(s) would beneficial.
If that Event Handler is a Saga or a regular Event Handling Component, is up to you.

I’d however recon that in the sketched use case it’s better to use a Saga.
Main reasoning for using a Saga is because you want to cope with scenarios where reserving a Book for a given Order didn’t work, because the Book was already reserved or it is being repaired, for example.

That’s my two cents, hope it helps!

Cheers,
Steven

Hi! Thanks for reaching out again. I’m quite new to Axon so it is still blurry for me.
I get what you mean but it is not obvious for me how do I handle that situation really. How could I identify if ReserveCommand would work? Would I have to create ReserveCommand in such a way that Book aggregate could identify order that “reserves” book?
What is the responsibility segragation between Order aggregate and Order Saga here? Do I even need Order aggregate?

Hi Jędrzej,

Whether a command was successful or not depends on the result of the command.

If you use the CommandGateway with send, you get a CompletableFuture, which can be resolved successfully or exceptionally.
Thus you can tell whether command handling went okay or not.

Whether your scenario requires an Order Aggregate at all as a hard question to answer, because I am not the domain expert in that situation.
Additionally, this is of course not so much an Axon specific problem, more so a modelling exercise anybody who’s doing DDD/CQRS/ES has to go through.
We can definitely give guidance on that, but I’ll always answer questions like these with an ‘it depends’, as that’s typically the case…

From what I grasp from your use case, I’d think having both an Order aggregate to mark that boundary and a Saga coordinating the operation of reserving Books for a given Order is the way to go.

An ‘OrderSaga’ does not necessarily sound right, as it’s not the order per say, but the delegation of an order and it’s book(s). Thus I’d look for a different term there.

Any how, Jędrzej, like I said, modelling is a tough cookie to get right.
I hope what I’ve shared here helps you a little.
If not, I’d recommend reading some blogs/books about the topic of DDD, maybe that’ll clarify things; that definitely got me up to speed. :slight_smile:

Cheers,
Steven