Best practices, guidance, and anti-patterns to avoid when building an application following CQRS/ES principles.
Services share schema and contract, not class.
Don't share types (i.e. .NET classes compiled into assemblies) as a way to express contracts in a messaging system.
Do define and share a schema and contract. Publish it for consumers to build their own internal representation of your data.
- At the Boundaries, Applications are Not Object-Oriented by Mark Seemann
- Data/Contract Coupling in Messaging by Clemens Vasters.
Do use simple types in domain events (strings, numbers, lists).
Don't use value objects in events as events are immutable, whereas value objects may change over time.
Don't expose domain events outside of the service, or bounded context, that creates them.
Do use integration events. An event that occurred in the past within an bounded context which may be interesting to other domains, applications or third party services. The purpose of integration events is to explicitly propagate committed transactions and updates to additional subsystems. You may convert and publish a domain event to external services, as an integration event, after it has been committed.
This makes is easier to change internal domain events, while allowing external integration events to provide a stable API to consumers. It also allows a service to only expose a subset of its domain events as integration events. Integration events can be enriched with additional data, such as from a read model projection, that is relevant to external uses.
Don't share data between projections.
Do use autonomous projectors responsible for their own data. Projectors should run independently, allowing them to track their own progress and be rebuilt as required.
Don't enforce consistency rules in your read model projection.
Do follow Postel's robustness principle:
Be conservative in what you send, be liberal in what you accept.
Pull requests gladly accepted for your best practices, advice, and guidance.