There are a lot of opinions out there in terms of CQRS with DDD and what makes up each component. I haven't began to look into Event Sourcing yet, so the list below doesn't include anything related to that. Although insight into ES would be interesting.
So far I have the following components with associated responsibilities (see below). I have inlined some questions in the points below.
REST Endpoints / Application
- Receive request from user / ui / etc
- Construct and dispatch relevant Command
- If the command requires values from other bounded contexts, then perform the relevant Finder calls required to instantiate the command correctly (e.g. Order requires User ID)
- In the case of a GET: the relevant Finder is called
- Finders sit at this level of the application. The Bounded Contexts write side (command handler, aggregate, factory, domain service, etc) should not be calling Finders. This will maintain encapsulation and by passing into the commands only the required data (instead of full DTOs) it becomes a modest Anti-Corruption layer.
- For example:
AggregateId orderId = AggregateId.get();
AggregateId userId = finder.findUserAggregateIdByEmail(email);
dispatcher.fire( new CreateOrderCommand( orderId, userId, orderItems ) );
Command
- Changes to the domain are made by dispatching commands
- Commands are immutable and contain the data necessary for a Bounded Context to alter it's state or throw an exception
- The command input can be validated on object creation in order to avoid invalid commands being sent
- For example:
new CreateOrderCommand( orderId, userId, orderItems );
Command Handler
- The handler can either successfully apply the command or raise an exception
- There can only be one command handler for each command
- The handler will load or create the Aggregate Root (Repository or Aggregate Factory)
- The handler will apply the command to the aggregate root
- The handler deals with the repository
- Should not fire commands (in or out of it's bounded context)
- Should the command handler dispatch Events? For example after successfully saving to the Database? Or is this solely the Aggregate's responsibiity?
Aggregate Factory
- Encapsulates the logic required to initialise an Aggregate Root correctly
- The factory can access the repository
- Should the factory access Domain Services?
- For example:
factory.createOrder( orderId, userId, orderItems );
Aggregate Root / Aggregates
- Contains the domain logic, state and behaviour
- Responsible for dispatching Events
- Aggregate Root encapsulates access to Aggregates
- Aggregate Root should have an ID that uniquely identifies it
- Should not interact with external services (other than an event publisher)
- For example:
order.cancel();
Domain Service
- This contains what doesn't quite fit in an Aggregate Root
- What components can the domain service interact with?
- Should the domain service fire commands / events?
- For example: Not sure what to use here, the first point above is vague at best. Most behaviour sits nicely in the Aggregate or can be achieved through Sagas / Events / Commands. What would be a valid example here?
Repository
- Takes care of loading / saving / updating / etc our Aggregates
- For example:
repo.load(orderId);
Event
- Represents something that took place in an aggregate (or command handler, etc if they can also fire events)
- Events are immutable
- Other bounded contexts in the system can use an event to make decisions
- For example:
new OrderCancelledEvent( orderId );
Event Handler
- Reacts to an event that took place
- There can be multiple event handlers for a single event in the same or different bounded contexts
- Can interact with Infrastructure services: OrderCancelled => OrderCancelledHandler => EmailService.sendEmail()
- Can fire new commands
- Can talk to Finders
- As the event handler fires commands, talks to Finders and interacts with the infrastructure, it is similar in nature to the Saga (or the REST Enpoint behaviour). Except it is the reaction to a single event rather than a chain / set of events.
Saga
- Maintains a business process which sits across the same or multiple bounded contexts (co-ordinates)
- Receives Events
- Maintains the state of a chain / set of events
- Normally the state is persisted
- Timeouts can be set to check / alter state (can have notion of time)
- States can have side effects, such as: firing commands, talking to Finders, interacting with the infrastructure services (e.g. email)
- For example:
Wait for OrderShipped and OrderReceived events => fire CancelOrderCommand
Wait for OrderCancelled => fire order cancelled email
Finder
- Used to retrieve a readmodel of the context(s)
- Generally returns Data Transfer Object (DTO) type objects
- Finder should not be found in the write side of our application (less coupling)
- Single (read+write) Normalized Database Model: the Finder can call other Finders (across contexts) to satisfy nested objects
- Read Specific De-normalized Database Model: the Finder will get the data all in one Database call
- For example:
finder.findOrdersOnDate( date );
Infrastructure Services
- Deals with infrastructure: db access, send emails, message queues, etc
Question
Is this an accurate summary of the components vs. responsibilities?
What's missing and what should be moved around?
I can update the list with relevant answers.
What is CQRS API?
CQRS stands for Command and Query Responsibility Segregation, a pattern that separates read and update operations for a data store.
What is the use of CQRS?
CQRS is a popular architecture pattern because it addresses a common problem to most enterprise applications. Separating write behavior from read behavior, which the essence of the CQRS architectural pattern, provides stability and scalability to enterprise applications while also improving overall performance.
What is the difference between CQS and CQRS?
CQRS takes the defining principle of CQS and extends it to specific objects within a system, one retrieving data and one modifying data. CQRS is the broader architectural pattern, and CQS is the general principle of behaviour.
Like you said, there are many opinions out there, and you need to filter them as most of the time people are giving opinions without any experience on the matter. CQRS is a big topic so I don't think that without prior experience you should jump into DDD and ES altogether. Services should be well contained and with well defined boundaries and if you follow these principle you'll be able to have different implementations in your domain, so start with just CQRS now and add DDD/ES to the following services once you have mastered CQRS.
I would advice you so start building the CQRS part of your architecture, a gateway for the commands and one for the queries because that is common and just on that there are so many decisions to be made:
and start implementing your service in a more traditional way without DDD, just using the repository pattern. When you start feeling confident then maybe you can jump into DDD in terms of aggregates and later on to ES. You can always change the initial services at a later stage.
My advice would be not to try to do it all at once, because you will fail; I have seen it happening many times before.
For example: Wait for OrderShipped and OrderReceived events => fire CancelOrderCommand Wait for OrderCancelled => fire order cancelled email
Sagas should not publish events (saga pattern), sagas aggregate events and submit commands. The fact that frameworks like NServiceBus allow sagas to publish events does not help, so be aware.
Single (read+write) Normalized Database Model: the Finder can call other Finders (across contexts) to satisfy nested objects
What other contexts you want to have in your read models?
Infrastructure Services
Deals with infrastructure: db access, send emails, message queues, etc
Not sure what you mean by this, but it sure does not look right. Message queue or database service??