Bounded Context is a Team Working Together
Let's talk about architecture-level decisions that drive development and evolution of systems. As Stacy noticed recently:
I notice your architecture is at the BC level. I certainly can see some advantages. But I wonder about things growing into a big ball of mud as requirements dictate more aggregates or larger ones. By designing at the aggregate root level, things stay small an agile as the business requirements grow and change. Although the tradeoff is more message chatter between them.
Just wondering what drove you to the BC's as components, rather than aggregate roots?
I used to focus just on aggregates (taking the route of "your aggregate is your bounded context"). This looked plausible in theory, but it didn't work for me in practice that well.
Reason behind that - Aggregates are the most important building block of my systems, since they capture core business concepts and deal with complex persistence tasks (via event sourcing). However, focusing only on aggregates can lead to less efficient designs, that lack some synergies and capabilites. Such approach encourages putting too much responsibility in one place, which significantly raises complexity and cost of change (if you seen recent "Avengers" movie, you have an idea of what happens, when you put a team out of outstanding heroes - most of the time they argue and deliver collateral damage).
Besides, this actually leads to rather chatty implementations. Aggregates, no matter how significant they are, still need to cooperate and exchange information (e.g. consider the case with "Customer" and "Invoice" aggregates, where old invoice has to be cancelled, whenever an override invoice is issued for the same customer).
Solution to that problem of mine was to start thinking in terms of bounded contexts, rather than individual aggregates. Bounded Context (BC) is part of a domain model, unified by a common ubiquituous language, shared and connected concepts. It is like an organ of a human or a plant - composed from multiple separate cells, but with distinct boundaries (or, you can consider BC to be a country, if you prefer my war analogies).
As such, Bounded Context consists of multiple distinct building blocks that work together to simplify achieving common goals of capturing core business concepts (and aggregates are one of these building blocks).
Implementation-wise, Bounded Context is a highly specialized team of focused soldiers (e.g. A-team) that work together. They tend to be deployed together as well (which aligns them well with the development and operations). Software solution can be composed rom multiple bounded contexts.
Each arcitecture style has it's own set of building blocks. Common guidelines for picking them are:
- blocks have to be relatively simple and coherent on their own in theory;
- development, management and evolution of this building blocks should be practical.
In the case of doing Domain-Driven Design a la Lokad, we have arrived to the list of building blocks:
- Application Services - groups of command handlers, defined on a single class. Most of the time, these command handlers are responsible for loading aggregates and executing their methods (although, other uses are quite common, as well).
- Aggregates implemented with event sourcing; they capture behaviors and core business concepts in an expressive way that frees us from bothering about complex persistence, its testing and versioning.
- View Projections - event handling classes that denormalize events into persistent read models called "views". These views are complextely disposable and can be automatically rebuilt by server, if corresponding projection code changes. They are used for bringing information together in a way, that is easy to consume by: aggregates, clients, tasks etc.
- Event Ports - simple event handling classes that declare event subscriptions and act as ACL (previously called "Event Receptors", but switched the name to match "Ports and Adapters" by Alistair Cockburn). They make boundaries and relations - explicit.
- Tasks - long-running processes (in essence, just a
while(server.IsRunning) { DoSomething(); Sleep(server, 5.Mins()); }), that allow the system to actively invoke actions; keep track of timeouts and schedule actions.
In practice, not every bounded context has all of these blocks (for example, my client BCs tend to have only view projections). However, having them at hand (along with the available practices of development, deployment and operations) is what makes a lot of scenarios possible: starting from simple SaaS billing system and up to self-diagnosting big-data platform for integration (which integrates directly via databases with flexible schema) and business intelligence.
Please, keep in mind, that this choice of building blocks is by no means something final, officially approved, certified (hell, I'm merely bringing together and exposing knowledge of more experienced practitioners). So it is just a selection that has (so far) proven to work the best in Lokad and some other small result-oriented companies. These are the companies, where software designers and architects have wear the burden of responsibility about their decisions and choices for years. This is exactly the environment, where operations crush and crumble nice theoretical ideas, forging some other solutions that ain't that pretty. Yet, in practice they work.
Each block has a whole story behind. I hope eventually to share it (dude called Vaughn Vernon even strongly suggests on writing a separate book on that). However, what matters the most right now - this is minimal set of blocks that we would currently need to build a system in a case, which requires application of Domain-Driven Design (with or without capabilities for cloud deployments and big data processing). More is certainly possible, but that would be less.
This approach to structuring Bounded Contexts would certainly evolve and change, as simpler approaches are found and new tools show up in this area of knowledge (some of the future improvements in development landscape are already accounted for).
Takeaway
Aggregates are just one of the building blocks for expressing Bounded Contexts in the code. Aggregates implemented with event sourcing shine in capturing rich behaviors that might require both complex persistence AND rapid evolution of underlying schema. Domain-Driven Design and use of other building blocks is an essential prerequisite for this approach to succeed.
Sunday, July 22, 2012 at 19:33
Reader Comments (4)
Thank your for noting when you have opted for a name change like from "Event Receptors" to "Event Ports". This helps us keep track of the evolving changes by flagging things with "same concept, new name" vs. "this is a new concept you need to learn".
Speaking of vocabulary, how do you view and speak about the first "D" in DDD? "Domain" itself can be overloaded.
Do you view a "domain" as a container of multiple Bounded Contexts, "Salescast @ Lokad" or do you view Lokad the company as one big domain, "within the domain of Lokad, we have n Visual Studio solutions each covering a specific Bounded Context of the company"? Perhaps the "D" just means the specific models you define within the BC, "BCSample.Domain" folder in VS?
In your real Visual Studio solutions at Lokad would I see a VS solution that contains a project for each BC, "BC1.Domain", "BC2.Domain"', "BC3.Domain"...or is the single BC the main focus of each VS solution?
Just trying to visualize what "domain" means to you and how you structure it in your VS solution and project files.
Great write-up. Thanks for laying out the thinking behind the bits.
"Solution to that problem of mine was to start thinking in terms of bounded contexts, rather than individual aggregates. "
This is very insightful to me. We ride "up" the DDD concepts based on the problem we are trying to solve. Now here's the hat trick - imagine the BC as a simple wrapper for a group of related aggregates. In other words, the aggregate can stand alone, or either be plugged into a BC.
This makes sense to me because I think development starts at the aggregate root level and grows from there. That's where I'm at with my system, all aggregate components and no explicit notion of a BC, yet.
But what I have learned from your Lokad experience is that one day I might need to evolve into BC's. And I certainly would not want to have to change my aggregates much to do that. But rather plug them into a BC infrastructure that takes an aggregate which only knows how to receive a command and return events.
That said, my gut still tells me that aggregate root is the right component size for everything so far - Visual Studio solutions, complexity, deployment scenarios, versioning, outsourcing work, git repos, etc. Everything else is infrastructure.
@Kerry, I tried to answer your question in my next bog post.
@Stacy, one additional advantage of thinking at BC level, that I found quite useful (it is taken from Jeremie): since aggregates become less important and easier to communicate, it becomes easier to split them into different entities. For example, real-world customer "Northwind Corp" in a single system can be represented as "Customer", "Consumer" and "Security" (well and "Registration"). It can also have a matching "Account" in some other systems.
This leads to simpler and more focused aggregates (meaning - less risk and cost of change). However, in order for this mindset to work, bounded contexts need to be something a bit more, than a mere wrapper for a group of somewhat related aggregates. They need to be treated as a strong boundary around of group of tightly related concepts that also share some implementation features and just tend to work together well.
Obviously, this is not a rule (definitely not something written in any book), but rather a mindset that I found to be helpful in evolving my designs in cases, where aggregates are just not enough.
Hope this helps.