Archived post: posted sometime between 2016 and 2022.

Humble notes after five weeks migrating an existing domain driven design to CQRS/ES

DRAFT... time-spent (45-min)

This blog post is a work in progress. If anything doesn't seem quite right, please Tweet to me at https://twitter.com/shaunluttin .

The intended audience for this post is software developers that are familiar with CQRS/ES and want a story about someone who has been using it for five weeks.

Directory Structure

domain/
  models/
  commands/
  events/
  snapshotUpdateServices/
repositories/
  commands/
  queries/
infrastructure/
  Command.ts
  Event.ts
  EventStoreService.ts
  CommandHandler.ts                                                                           

Before introducing CQRS/ES, the existing system was following Domain Driven Design. The models/ directory contained Domain Models and the repositories/ directory contained interfaces for populating those models from some data store.

Adding CQRS/ES involved introducing the additional directories and also split repositories/ into command/ and queries/ .

Separation of Command/Query Responsibilities

In the canonical pattern, domain commands produce domain event models, which the system writes to the event store, and queries run against view models that materialize from the event store. That means that the "write model" consists of domain event models, the "read model"  consists of view models, and that both use the event store are their source of truth.

The snapshotUpdateServices/ exists because our existing system was already performing reads and writes against the domain model. Instead of moving away from that entirely, we changed our write model to domain events, which follows the canonical pattern, and introduced the snapshotUpdateServices/ , which listens to events as they occur and makes appropriate updates to the domain models. This is similar to the Materialized View pattern, with the difference that the snapshotUpdateServices/ update domain models instead of updating view-specific ones. This approach allowed us to keep our existing read system intact while migrating to CQRS/ES.