11
votes

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.

1
"Implementing Domain-Driven Design" by Vaughn Vernon provides technical details that cover each of these components. I'd recommend it as a very useful resource in addition to Eric Evans' Domain-Driven Design.Ben Smith
Coincidentally I found an article by Vaughn Vernon last night. Very interesting! I'll be buying the book. Anything you would add to the list based on the book?Alessandro Giannone

1 Answers

3
votes

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:

  • Rest API

  • Message contracts/validation

  • Read models ...

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??