Event Sourcing and CQRS, Bounded Contexts
By Jérémie Chassaing on Saturday, April 17, 2010, 15:21 - Domain Driven Design - Permalink
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.
Comments
Thanks Jeremie for great explanation.
Some things are still not clear to me though.
Let's take this into your example. Your inventory reporting BC displays list of lent and returned books, along with the name of the borrowers.
If you change your implementation to be more realistic by using BorrowerID in the event (instead of fullname), then how would this inventory BC retrive the view-models of the borrower?
It can subscribe to BorrowerRegistered event and store its own data of all borrowers that it can use for reporting purpose, but it would start to get messy.
It makes sense for inventory BC to keep its own view of Books concept (query-optimised view of Book), and manage the data based on BookRegistered, BookReturned, and BookLent events. But it does not make so much sense for it to also keep track of Borrowers, and BorrorwerRegistered/BorrowerTerminate events. They're beyond the UL of inventory BC.
And we probably have many other BC that requires borrower's full-name (e.g. late-book-notifier). And we have all other concepts like Publisher, Author, Genre, Country/Town, etc that are used in almost every BC. (ie, perhaps 80% of the time, a book, in any BC, is described by its title, publisher, and author's name). Yet you only propagate all these information (events) merely by their IDs.
How do other (querying) BCs understand these IDs? They need a complete descriptive information of the entity, not just the ID. Do they make a web-service call to a central BC to get these information? Who maintains these information?
Or alternatively does each of the BCs master its own set of storage for each of these entity-types by subscribing to e.g. AuthorAdded, AuthorModified, PublisherAdded, BorrowerMovedTown, etc?
If you could explain a bit of how you would implement book-borrower scenario into your example, that would help a great deal.
Thanks again :)
Just to associate with your example...
Command BC issues this event: ReturnedLateToShelf { Book : bookId, By : borrowerId, After : 20 days, LateBy : 6 days }
Relationship BC catches it, and it will want to inform the user (customer-service rep) about who the borrower is. It can't just tell the user: Borrower: #1534.
It needs to tell the user the name and email address of the borrower. Full-name and email aren't part of the participating events , but they certainly are part of the Borrower entity in this BC. This BC thus has BorrowerRepository I figure, but im not sure how it's then able to retrieve the complete aggragate root of Borrower entity?
If I intepret correctly, you tend to lean toward having this BC having its own storage of all borrowers? (And publisher, authors, etc)
@Hendry Luk> Actually the Inventory BC itself will not display lists, because the BC is part off the Command side.
To display lists, the web application (or web service, or...) will build a persistent view model. There will be a service that will handle Domain events a populate a denormalized view of the model, probably in a SQL table. The web site will simply display this list.
This service will be in charge to listen to events from all BCs to update Persitent View Models (usually one per page).
This is important because display screen often gather information from several contexts. But the contexts themselves don't have to know this, since these aggregations are only for display (Querying/Reporting) purpose.
So, the Inventory BC doesn't know about borrowers names, the Relationship BC doesn't know about books names, but the denormalizers listen to both and can prepare the persitent view model.
"Actually the Inventory BC itself will not display lists, because the BC is part off the Command side."
Ah now I see... I thought each BC would have both command and query sub-BCs.
In many applications, a BC is a whole complete application by itself with its own UI and sometimes storage. I was thinking that inventory BC is an inventory-management application which includes its GUI client, hence the necessity of displaying the list of books, their publishers, authors, and perhaps borrowers (albeit not good example).
I didnt realise that BCs separation only happens within command-side of thing. Most of my readings have always led to understanding that each BC will wrap a command-component, and a query component.
So, for the sake of dicussion, in the case that inventory has its own separate UI application, were you saying that this will then listen to all various events (PublisherAdded, AuthorsModified, BorrowerSignedUp) and maintain its own set of "master-data", so when it receives a BookRegisteredEvent it knows how to create a flat denormalised view-model out of it?
Cheers
@Hendry Luk> You can also split Query BCs, but based on Query requirements. A single Query BC will usually gather information from serveral Command BCs. Because presentation applications often display crossed informations from different part of your domain.
Hi,
This is a helpful post, and I like the idea of not storing larger fields like names/descriptions in the state of domain objects... but how much does this really help in terms of the overall loading time of an aggregate root?
Don't you still have to retrieve all the events since the last snapshot, and wouldn't those event objects contain all the actual values, including names and descriptions? (due to events like NameChanged) Once the events were applied of course, I guess you wouldn't need to store them in memory anymore...but it could still be a lot of data to load in the first place, especially for a large aggregate root.
BTW, just discovered your blog...it's been really great at helping me understand how CQRS and event sourcing work...
Thanks
@Matt> Actually, since you'll keep aggregates in memory, the loading happens only once at startup time.
This is the kind of place you you can incure a few second delay if you have many aggregates to load.
And as you use message queueing for commands, this only adds a small latency into your system, but the overall service is still running.
Thanks for the clarification, that makes sense.