I'm working on a small example application to learn the concepts of CQRS and event sourcing. I have a Basket
aggregate and a Product
aggregate which should work independently.
Here's some pseudo code to show the implementation
Basket { BasketId; OrderLines; Address; }
// basket events
BasketCreated { BasketId; }
ItemAdded { BasketId; ProductId; Quantity }
AddItemSucceeded { BasketId; ProductId; Quantity }
AddItemRevoked { BasketId; ProductId; Quantity }
ItemRemoved { BasketId; ProductId; Quantity }
CheckedOut { BasketId; Address }
Product { ProductId; Name; Price; }
// product events
ProductReserved { ProductId; Quantity }
ProductReservationFailed { ProductId; Quantity }
ProductReservationCancelled { ProductId; Quantity; }
Commands are pretty similar to the events, using the imperative name and not past tense.
Right now these work just fine independently. I issue a command AddItem
, and it creates a ItemAdded
event on the Basket
aggregate which does what it needs to do with the state of the 'Basket'. Similarly, for product the command and events work just fine.
I'd now like to combine this into a process which would go something like this (in terms of commands and events that happen):
The process manager would do the following:
on BasketCreated: CreateShoppingProcess
on ItemAdded: ReserveProduct
on ProductReserved: SucceedAddingItem // does nothing, but needs to be there so that the basket knows it can check out
on ProductReservationFailed: RevokeAddItem
on RemoveItem: CancelProductReservation
on Checkout: CreateOrder // create an order and so on...
The questions that I couldn't find definitive answers to are:
- Do I need to persist the process manager? It seems like I do, but I'm not sure
- If I do, I need to save the events for the process manager. However, the events that It's listening to are tied to the aggregates. Do I add the process id to those? Do I have separate events just for the process manager? How to do this and keep as DRY as possible
- How do I know what basket the
ProductReserved
events are for? Is it OK to have aBasketId
on those too, or is that leaking info? - How do I keep a relationship between events, how do I know which
ItemAdded
produced whichProductReserved
event? Do I pass along anEventId
? This seems odd... - Should I implement the
Basket
as a process manager instead of a simple aggregate?
After some more research I came to this: A Saga is something that keeps its own events and listens to events from the outside. Basically, it's an Aggregate that can also react to events happening outside it's own little world.
A Process Manager works with the events from the outside and sends out commands. It's history can be rebuilt from the events that have happened on the Aggregates which share a common identifier like a correlationId.