Created
June 16, 2016 21:01
-
-
Save luque/51422fd4143d2293b4230e294b11b809 to your computer and use it in GitHub Desktop.
Notes on Exploring CQRS and Event Sourcing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Journey 1. Our Domain | |
---------------------------------------------------------------------- | |
* Overview of the system | |
* Nonfunctional requirements | |
** Scalability: | |
Although cloud platforms such as Azure enable you to scale applications by adding (or removing) role instances, you must still design your application to be scalable. By splitting responsibility for the application's read and write operations into separate objects, the CQRS pattern allows Contoso to split those operations into separate Azure roles that can scale independently of each other. This recognizes the fact that for many applications, the number of read operations vastly exceeds the number of write operations. This gives Contoso the opportunity to scale the conference management system more efficiently, and make better use of the Azure role instances it uses. | |
** Flexibility: | |
Journey 2. Decomposing the domain | |
---------------------------------------------------------------------- | |
* Bounded contexts in the conference management system | |
** The Orders and Registrations bounded context | |
** The Conference Management bounded context | |
** The Payments bounded context | |
** The Discounts bounded context | |
** The Occasionally Disconnected Conference Management client | |
** The Submissions And Schedule Management bounded context | |
* The context map | |
A frequent comment about CQRS projects is that it can be difficult to understand how all of the pieces fit together, especially if there a great many commands and events in the system. Often, you can perform some static analysis on the code to determine where events and commands are handled, but it is more difficult to automatically determine where they originate. At a high level, a context map can help you understand the integration between the different bounded contexts and the events involved. Maintaining up-to-date documentation about the commands and events can provide more detailed insight. Additionally, if you have tests that use commands as inputs and then check for events, you can examine the tests to understand the expected consequences of particular commands. | |
1. Events that report when conferences have been created, updated, or published. Events that report when seat types have been created or updated. | |
2. Events that report when orders have been created or updated. Events that report when attendees have been assigned to seats. | |
3. Requests for a payment to be made. | |
4. Acknowledgement of the success or failure of the payment. | |
** Why did we choose these bounded contexts? | |
During the planning stage of the journey, it became clear that these were the natural divisions in the domain that could each contain their own, independent domain models. It has clearly defined responsibilities that relate to defining conferences and seat types and clearly defined points of integration with the rest of the application. | |
Journey 3: Orders and Registrations Bounded Context | |
---------------------------------------------------------------------- | |
* Working definitions | |
** Command | |
** Event | |
** Process Manager | |
* Domain definitions (ubiquitous language) | |
** Attendee | |
** Registrant | |
** User | |
** Seat assignment | |
** Order | |
** Order item | |
** Seat | |
** Reservation | |
** Seat availability | |
** Conference site | |
* Patterns and concepts | |
** Different aggregates alternatives: | |
*** OrderAggregate y SeatsAvailabilityAggregate | |
*** ConferenceAggregate: Order entity y SeatsAvailability entity | |
*** ProcessManager to coordinate the interaction between two aggregates: RegistrationProcessManager, OrderAggregate, SeatsAvailabilityAggregate. | |
The team identified the following questions about these approaches: | |
- Where does the validation that there are sufficient seats for the registration take place: in the Order or SeatsAvailability aggregate? | |
- Where are the transaction boundaries? | |
- How does this model deal with concurrency issues when multiple registrants try to place orders simultaneously? | |
- What are the aggregate roots? | |
*** Transactions: | |
In the DDD approach, represents a consistency boundary. | |
To ensure the consistency of the system when a registrant creates an order, both transactions must succeed. To guarantee this, we must take steps to ensure that the system is eventually consistent by ensuring that the infrastructure reliably delivers messages to aggregates. | |
In the second approach, which uses a single aggregate, we will only have a single transaction when a registrant makes an order. This appears to be the simplest approach of the three. | |
*** Concurrency | |
This reservation system introduces the need for additional message types; for example, an event to report that a registrant has made a payment, or report that a timeout has occurred. | |
Modeling this complex behavior with sequences of messages and the requirement for a timer is best done using a process manager. | |
** Aggregates and aggregate roots | |
In the two models that have the Order aggregate and the SeatsAvailability aggregate, the team easily identified the entities that make up the aggregate, and the aggregate root. The choice is not so clear in the model with a single aggregate: it does not seem natural to access orders through a SeatsAvailability entity, or to access the seat availability through an Order entity. Creating a new entity to act as an aggregate root seems unnecessary. | |
The team decided on the model that incorporated a process manager because this offers the best way to handle the concurrency requirements in this bounded context. | |
Reference 1: CQRS in context | |
---------------------------------------------------------------------- | |
CQRS pattern is not intended for use as the top-level architecture of your system; rather, it should be applied to those subsystems that will gain specific benefits from the application of the pattern. | |
* What is domain-driven design? | |
As previously stated, DDD is an approach to developing software systems, and in particular systems that are complex, that have ever-changing business rules, and that you expect to last for the long term within the enterprise. | |
The core of the DDD approach uses a set of techniques to analyze your domain and to construct a conceptual model that captures the results of that analysis. You can then use that model as the basis of your solution. The analysis and model in the DDD approach are especially well suited to designing solutions for large and complex domains. | |
- In focusing on the domain, DDD concentrates on the area where the business and the development team must be able to communicate with each other clearly, but where in practice they often misunderstand each other. The domain models that DDD uses should capture detailed, rich business knowledge, but should also be very close to the code that is actually written. | |
- Domain models are also useful in the longer term if they are kept up to date. By capturing valuable domain knowledge, they facilitate future maintenance and enhancement of the system. | |
- DDD offers guidance on how large problem domains can be effectively divided up, enabling multiple teams to work in parallel, and enabling you to direct appropriate resources to critical parts of the system with the greatest business value. | |
* Domain Model | |
This model is built by the team responsible for developing the system in question, and that team consists of both domain experts from the business and software developers. The domain model serves several functions: | |
It captures all of the relevant domain knowledge from the domain experts. | |
It enables the team to determine the scope and verify the consistency of that knowledge. | |
The model is expressed in code by the developers. | |
It is constantly maintained to reflect evolutionary changes in the domain. | |
* Ubiquitous language | |
If both the domain experts and the developers use the same terms for objects and actions within the domain (for example, conference, chair, attendee, reserve, waitlist), the risk of confusion or misunderstanding is reduced. More specifically, if everyone uses the same language, there are less likely to be misunderstandings resulting from translations between languages. | |
* Entities | |
Entities are objects that are defined by their identity, and that identity continues through time. | |
* Value objects | |
Not all objects are defined by their identity. For some objects—value objects—what is important are the values of their attributes. For example, within our conference management system we do not need to assign an identity to every attendee's address (one reason is that all of the attendees from a particular organization may share the same address). All we are concerned with are the values of the attributes of an address: street, city, state, and so on. Value objects are usually immutable. | |
* Services | |
You cannot always model everything as an object. For example, in the conference management system it may make sense to model a third-party payment processing system as a service. The system can pass the parameters required by the payment processing service and then receive a result back from the service. Notice that a common characteristic of a service is that it is stateless (unlike entities and value objects). | |
Services are usually implemented as regular class libraries that expose a collection of stateless methods. | |
* Aggregates and aggregate roots | |
Whereas entities, value objects, and services are terms for the elements that DDD uses to describe things in the domain model, the terms aggregate and aggregate root relate specifically to the lifecycle and grouping of those elements. | |
When you design a system that allows multiple users to work on shared data, you will have to evaluate the trade-off between consistency and usability. At one extreme, when a user needs to make a change to some data, you could lock the entire database to ensure that the change is consistent. However, the system would be unusable for all other users for the duration of the update. At the other extreme, you could decide not to enforce any locks at all, allowing any user to edit any piece of data at any time, but you would soon end up with inconsistent or corrupt data within the system. Choosing the correct granularity for locking to balance the demands of consistency and usability requires detailed knowledge of the domain: | |
DDD uses the term aggregate to define a cluster of related entities and value objects that form a consistency boundary within the system. That consistency boundary is usually based on transactional consistency. | |
An aggregate root (also known as a root entity) is the gatekeeper object for the aggregate. All access to the objects within the aggregate must occur through the aggregate root; external entities are only permitted to hold a reference to the aggregate root, and all invariants should be checked by the aggregate root. | |
* Bounded contexts | |
For a large system, it may not be practical to maintain a single domain model; the size and complexity make it difficult to keep it coherent and consistent. To manage this scenario, DDD introduces the concepts of bounded contexts and multiple models. Within a system, you might choose to use multiple smaller models rather than a single large model, each one focusing on some aspect or grouping of functionality within the overall system. A bounded context is the context for one particular domain model. Similarly, each bounded context (if implemented following the DDD approach) has its own ubiquitous language, or at least its own dialect of the domain's ubiquitous language. | |
There are no hard and fast rules that specify how big a bounded context should be. Ultimately it's a pragmatic issue that is determined by your requirements and the constraints on your project. | |
You decide which patterns and approaches to apply (for example, whether to use the CQRS pattern or not) within a bounded context, not for the system. | |
* Anti-corruption layers | |
Different bounded contexts have different domain models. When your bounded contexts communicate with each other, you need to ensure that concepts specific to one domain model do not leak into another domain model. An anti-corruption layer functions as a gatekeeper between bounded contexts and helps you keep your domain models clean. | |
* Context maps | |
A large complex system can have multiple bounded contexts that interact with one another in various ways. A context map is the documentation that describes the relationships between these bounded contexts. It might be in the form of diagrams, tables, or text. | |
* Bounded contexts and multiple architectures | |
A bounded context typically represents a slice of the overall system with clearly defined boundaries separating it from other bounded contexts within the system. If a bounded context is implemented by following the DDD approach, the bounded context will have its own domain model and its own ubiquitous language. Bounded contexts are also typically vertical slices through the system, so the implementation of a bounded context will include everything from the data store, right up to the UI. | |
The same domain concept can exist in multiple bounded contexts. For example, the concept of an attendee in a conference management system might exist in the bounded context that deals with bookings, in the bounded context that deals with badge printing, and in the bounded context that deals with hotel reservations. From the perspective of the domain expert, these different versions of the attendee may require different behaviors and attributes. For example, in the bookings bounded context the attendee is associated with a registrant who makes the bookings and payments. Information about the registrant is not relevant in the hotel reservations bounded context, where information such as dietary requirements or smoking preferences is important. | |
One important consequence of this split is that you can use different implementation architectures in different bounded contexts. | |
For example, one bounded context might be implemented using a DDD layered architecture, another might use a two-tier CRUD architecture, and another might use an architecture derived from the CQRS pattern. | |
* Bounded contexts and multiple development teams | |
Clearly separating out the different bounded contexts, and working with separate domain models and ubiquitous languages also makes it possible to parallelize the development work by using separate teams for each bounded context. This relates to the idea of using different technical architectures for different bounded contexts because the different development teams might have different skill sets and skill levels. | |
Reference 2: Introducing the CQRS | |
---------------------------------------------------------------------- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment