Event Sourcing and CQRS, Bounded Contexts
Once again, I prefer a new post that a long comment reply. This one is about a important concept of Domain Driven Design, Bounded Contexts.
Hendry Luk asked :
Just 1 question, you represent borrower in events as a simple full-name string.
Is there any reason or just for sake of simplicity for example?
Supposed I'm using borrowerId, how would that work in other BC, say
LateBookNotifier (let's assume its a separate BC). How does this BC shows the
name of the borrower? Does it communicate directly with command BC using ACL?
Or does it also subscribe to BorrowerRegistered event as well (hence every BC
would have duplicate data of each of the borrowers, just like they do each of
the books)?
The short answer is ‘Yes, it was just for sake of simplicity’. In a real world scenario, borrowers would probably be entities, and thus would have an identity. I would even probably be an Aggregate Root.
The Borrower Aggregate Root would encapsulate state needed to perform commands on this Aggregate.
Bounded Contexts Communications
I can see the following contexts here :
- Inventory : Manage books availability and state (the book has been damaged, there a notes written on it etc..)
- Relationships : Manage contact by email, phone with borrowers, and tracks the care they take to your books, if they return it on schedule.
Since we are using CQRS (and even more, Event Sourcing), aggregates in these context don’t need more state that what’s needed to take decisions,
So a Book in the Inventory Context will probably not need more that the Id of the borrower and the date a witch it was borrowed.
We can then call the ReturnToShelf command on the Book that will publish a ReturnedLateToShelf { Book : bookId, By : borrowerId, After : 20 days, LateBy : 6 days }.
A Handler at the Relationships Boundary will catch the event, and call a CheckExcuseForLateReturn on the Borrower Aggregate Root (based on its id). The command will check the borrower’ss record to see if its acceptable. It will simply publish a LateReturnGentlyAccepted if the borrower is usually in time, but will publish a KindnessLimitReached in the other case.
Another handler will catch it, and call SendAngryMessage on the Messaging Service. The role of the Messaging Service is to tweet borrowers to let them know they should not forget to return your books. How does this service know the twitter account of the borrower ? When the handler (the one that call SendAngryMessage) catches a BorrowerRegistered event or a BorrowerTwitterAccountChanged message, it says so to the service that can maintain a list of accounts in any desired storage (SLQ, NoSql, in memory.. ?). The SendAngryMessage can now tweet ‘Hey you filthy @borrower, you better return my book today or I shall share all the pics from your last party…’
Done.
Where does data live ?
There’s usually a huge concern about data duplication in all contexts. Is the info duplicated in so many places ?
There will be two main places :
- The Persistent View Model used to see and edit borrower’s details
- The Persistent View Model used by the messaging service to Query borrower’s twitter accounts. Here, no other borrower’s data is needed except its id and account name.
The Borrower Aggregate Root and Book Aggregate Root in the two main Domain Bounded Contexts will not need to keep track of this kind of data. They won’t need it in their decision process.
If you pursue this idea, to answer further to Leonardo, you’ll notice that strings will probably never been used as state inside Domain Bounded Context. They can appear as identity key, or just pass through a command and be republished in the following event. But since strings are rarely – if never – a good way to represent information on which you’ll have to take a decision, it should almost never be stored in an aggregate root current state. This is another reason why most domain models can fit in memory, because names, descriptions and other documents usually represent the biggest part of the data in a system, the remaining data is usually small. These documents and names are useless to run domain internal logic (except validation rules, but not state change rules) so they can simply be logged in events and persisted in the Query’s View Models. Only state needed to take state change decisions will stay in memory.