This is yet another post in a series that were triggered by fruitful discussions with Vaughn Vernon over content for his DDD book.
I think, one of the sources of confusion in DDD/CQRS world is that we often mix terms and concepts that belong do absolutely different layers (and that we don't know how to go from one to the other). Let's start by introducing the following separation:
For the new readers, DDD stands for Domain Driven Design, which was introduced by Eric Evans in the book with the same name. CQRS stands for Command-Query Responsibility Principle, which is often associated with architecture styles for implementing systems with DDD and optional Event Sourcing. The term was coined and explored by Greg Young.
Reality is that thing around us, which we perceive through our senses and continuously try to understand. In the context of business and software, reality contains core business concepts which are important for the competitive advantage of our business. We want to capture them and then somehow express in code for automation purposes. For example, business concepts could involve things as:
- Registration Process
- Customer Subscription
- Invoice Payment Cycle
I'm taking examples from the environment of Software-as-a-Service (SaaS) company, since that's what I'm mostly familiar with.
As we learn more about reality and business concepts, we could distill our understanding into the domain model, which contains all things that are relevant and important in the current situation. For the sake of simplicity, we will break down the entire model into set of bounded contexts (BCs) which are separate by the natural boundaries we've discovered in the real world.
In SaaS world we could highlight BCs like:
- Customer Subscriptions and Billing
- Client Portal
- Product 1
- Product 2
- Cloud integration
Each of the bounded contexts in this model stems directly from our understanding of the reality and the natural boundaries that we have identified (read more).
If we dive inside one of these bounded contexts, we could discover more fine-grained concepts:
- Ubiquitous Language
- Aggregate and Aggregate Root
- Consistency Boundary
- Business Process
Please keep in mind, that these are purely logical concepts, that have (yet) nothing to do with the implementation and all the less important details!
The process of identifying BC boundaries can take into consideration things like: teams, skills, available resources and technologies. However, at this level we still don't care about technical details like: frameworks, databases, message middleware, service buses etc. We just create foundation for making conscious choice later down the road.
Architecture Styles and Implementation
Only after we have identified bounded contexts, we can focus on each BC and start thinking about implementation matters, while considering project specifics. Result of this exciting process would be a choice of key elements for the specific bounded context:
- development process - how do we organize and manage our development.
- architecture style - how do we structure and design software implementation.
- technology stack - what technologies do we use and how do we get them
- resource allocation - how do we get resources (budgets, people, knowledge) for the project delivery.
For instance, if our teams are familiar with the SQL/RavenDB and NService Bus, we can pick architecture style described by Udi Dahan (blog), where:
- aggregates are persisted with SQL+NHibernate or RavenDb;
- command handlers and application services are hosted by NServiceBus;
- business processes are implemented with NServiceBus sagas;
- consistency boundaries happen within the transaction scope;
- views are created either by in-memory events or via projected audit logs;
- development process will be aligned towards Waterfall or Agile.
If environment requires event sourcing or teams are hyped with AR+ES architectural style of Greg Young (blog), we can:
- persist aggregates with event sourcing in event streams;
- host command handlers in custom message dispatchers that use something like AMQP or direct socket communication;
- business processes are mainly implemented with state machines hosted within event handlers or via document-based state machines (where state is persisted in messages);
- views are disposable and are projected from event streams to whatever technology that is needed;
- consistency boundaries are within aggregate;
- use Agile development process.
At Lokad we are mostly using Lokad.CQRS architecture style (sample) that is derived from Greg's but is fine-tuned to:
- fit cloud computing environments while supporting on-premises deployments;
- reduce development friction and development effort at the cost of higher requirements for team skills and discipline;
- support rapid domain evolution in rapidly changing business environment.
This architecture style involves following technical choices:
- aggregates are persisted with event sourcing in event stream;
- message handlers are hosted in custom message dispatchers provided by Lokad.CQRS sample project (with adapters for on-premises and cloud deployments);
- business process transitions are implemented as part of the aggregate behaviors (they could be triggered by user interactions, stateless event handlers sending commands in response to events or tasks that sending commands on a schedule);
- views are disposable and are projected from event streams, using dead-simple key-value persistence for the majority of cases (with adapters for on-premises and cloud);
- consistency boundaries are per entity (aggregate or view instance);
- rapid development process is used for multiple releases per week/day.
Obviously, these are just a few options of implementing a given bounded context. There are more architecture styles available out there (and each architecture style can have multiple implementation options and variations).
DDD Modeling Process
Now, for the most fun part. This trajectory from reality to architectural style is just a happy path scenario that happens only in dreams. In reality you might need to iterate from reality to implementation multiple times:
These iterations are the foundation of the approach to explore and capture core business concepts in domain model (or, one of the approaches). Approach is totally attributed to Greg Young.
We start by sketching out domain model without even trying hard to be precise from the start. Then we try to implement it in the code using the most hacky approach possible. Lokad.CQRS style with file-based persistence and messaging works for us, because it is designed for rapid iterations and has the least friction (I'll need to do a quick video on that process).
One of the goals here is to build a set of unit tests that use specifications to verify behaviors of aggregate roots with event sourcing (AR+ES). These specifications can be printed out as use cases in human-readable way.
More often than not, a lot of problems and questions show up during this implementation phase. At this point we usually forget about the implementation (while keeping our use cases) and go back to the domain experts with these questions (more often than not, for me this boils to going and talking to the mirror). Then we adjust the domain model according to the lessons learned. Depending on the nature of the adjustment, implementation is either discarded completely or refactored to fit (AR+ES specifications are usually kept between these iterations, to make sure that we don't loose any captured use case).
The process is repeated till we distill our domain model to the point that it captures all required business concepts in a way that can be easily implemented in the code. At this point we can print out specifications, reconfigure implementation to use adapters for production environment (e.g. Azure), push it to production and try to call it a day (only to have next challenge handed to us).
PS: If you are interested in this topic, next article in the series might interest you: DDD: Evolving Business Processes a la Lokad.