Implementing Domain-Driven Design Cheat Sheet
What's this?
The aim of this article is to provide a cheat sheet of the book Implementing Domain-Driven Design. It can specially be useful for when you are arguing with your colleagues over the concepts, things like "Can Domain Services use Repository interfaces?" and you don't feel like searching the whole book again
Do you need to migrate to DDD
Answer these questions and sum up your score. If you score 7 or above, then you need to move into DDD:
----------------------
Anemic Domain Model
If your answer to these questions are yes, then most probably you have an anemic domain model:
----------------------
Design is the code and the code is the design
Problem space:
Solution space:
The Context Map above can be considered an ideal design in a sense that each Subdomain is covered with only one Bounded Context and each Bounded Context spans across only one Subdomain, but this is not the case for most complex domains.
Bounded Context contains:
In this picture D means: down-stream and U means up-stream (down-stream depends on up-stream)
----------------------
Entity: Has Identity
Don't expose properties with setters. Instead have operations that mutate the Entity and its invariants.
Identity Generation
Use Value Objects for an Entity's Identity, e.g.: a ProductId class/struct or record (in C#)
Surrogate Identity
For the sake of fast joins and other persistence performance considerations, we may create an ID for this purpose on an Entity (usually Integer or Long datatype)
Self Encapsulation
Access to properties goes through accessor methods, even setting properties in a constructor (Martin Fowler). These setter methods, ensure data validity.
Validation
class Book {
? ? private _title: string = ''
? ? private _isPublished: boolean = false
? ? private _publishedAt: Date | undefined
? constructor(
? ? ? title: string,
? ? ? isPublished: boolean,
? ? ? publishedAt: Date | undefined
? ? ) {
? ? this.setTitle(title)
? ? this._isPublished = isPublished
? ? this._publishedAt = publishedAt
? }
?
? public title() {
? ? return this._title
? }
?
? // self encapsulation
? private setTitle(title: string): void {
? ? if (!title) {
? ? ? throw new InvalidBookTitleError('Book title not specified')
? ? }
? ? if (title.length > 128) {
? ? ? throw new LongBookTitleError('Book title is too long')
? ? }
? ? this._title = title
? }
?
? public isPublished(): boolean {
? ? return this._isPublished
? }
? public publishedAt(): Date | undefined {
? ? return this._publishedAt
? }
? // an example operation instead of two separate setters
? public publish(): void {
? ? ? this._isPublished = true
? ? ? this._publishedAt = new Date()
? }
}
Look at comments in the example Entity above. We encapsulate the logic that sets 'title' property and this allows us to first check that it always has some value and second make sure that it's not a long string. Most of the times you may want to avoid checking things like length or security things like making sure a string is a valid Email address, because this is an Application Service concern.
----------------------
Value Object: immutable and measures, quantifies or describes
class Name {
? ? private _firstName: string = ''
? ? private _lastName: string = ''
? ? constructor(
? ? ? ? firstName: string,
? ? ? ? lastName: string
? ? ) {
? ? ? ? this.setFirstName(firstName)
? ? ? ? this.setLastName(lastName)
? ? }
? ? private setFirstName(firstName: string): void {
? ? ? ? // some validation
? ? ? ? this._firstName = firstName
? ? }
? ? private setLastName(lastName: string): void {
? ? ? ? // some validation
? ? ? ? this._lastName = lastName
? ? }
? ? public get firstName(): string {
? ? ? ? return this._firstName
? ? }
? ? public get lastName(): string {
? ? ? ? return this._lastName
? ? }
}
entity.fullName = new Name('Saeed', 'Farahi')
// now we want to change the last name
entity.fullName = new Name('Saeed', 'Farahi Mohassel')
领英推荐
----------------------
Domain Services: Multi-Entity calculations, validation or operation
----------------------
Domain Events: in the whole Domain
----------------------
Aggregates: Cluster of Entities & Value Objects
----------------------
Factories
----------------------
Repositories: Collection-Oriented or Persistence-Oriented
Repositories host only Aggregates
----------------------
Integrating Bounded Contexts
Using RESTful, SOAP, RPC
Using Messaging
This is how a Domain Event is propagated in a system using messaging infrastructure. There are however two important requirements: 1. ensure handling events in the order they were occurred and 2. not applying an event twice. For requirement number 1 each Event can have a Date field and consumers can track the dates of events applied in their systems. For requirement number 2 an Event can have a globally unique ID (UUID/ObjectID) to allow idempotency in consumers.
----------------------
UI & Application
User Interface
In a application with remote clients e.g. in Web 2.0 applications, a User Interface can have Controllers (RESTful), Queries and Mutations (GraphQL), ..
In Web 1.0 applications such as ASP.NET or Spring MVC, UI will also contain Presentation Model (or View Model)
Data Transfer Objects
We return data to clients in the form of Data Transfer Objects (DTOs). The Application Service will use Repositories to read the necessary Aggregate instance or call custom optimal use case query to read some Value Objects, then delegates to a DTO Assembler to map the attributes of the DTO.
DTOs are always trivially serializable and never contain any behavior.
Now, Aggregates must provide some methods for DTO Assemblers so that they can query for necessary data. These methods can reveal internal state of Aggregates to outer world which is a dangerous path. Tight coupling between DTO Assembler and Aggregates can be avoided with Mediators (aka Double Dispatch and Callback).
class Customer {
provideCustomerInterest(interest: CustomerInterest): void {
interest.informId(this.customerId().value)
interest.informName(this.name())
}
}
In this example, interest is the Mediator and Customer Aggregate informs it with various internal information.
Application Services
Application services should accept/return primitive data types and DTOs and should avoid returning Domain Objects, because all clients will need to handle them separately.
They manage transactions and security and implement use cases by executing domain models.