DDD: Overview
Domain Driven Design (DDD)
DDD is a software development methodology that focuses on identifying business domain and designing solutions that solves the problem domain. DDD utilises both strategic design and tactical design. Strategic design provides high level architecture and tactical design is used for low level architecture.
Domain
A domain is the area of information and activities where the logic in the application involve. The business logic is encapsulated in the domain. The domain can be different from the perspectives of the users. Business rules also changes over time. Therefore, the changes in the software application is inevitable. DDD is aiming to reduce complexities in the software and reflect requirements flexibly by improving communication in a team.
Domain Model
The domain model consists of entities and values objects.
Entity
Each entity has a unique identity. In Product Catalogue domain, each product has an identifier (such as sku). The entity class may have the implementation of the methods like Equals()
and GetHashCode()
.
public class Product
{
private string sku;
public Product()
{
//...
}
public override bool Equals(Object obj)
{
if (! (obj is Product) || obj == null) return false;
if(this.sku == null) return false;
Product p = (Product) obj;
return this.sku.Equals(other.sku);
}
public override int GetHashCode()
{
return sku.GetHashCode();
}
}
A unique identifier can be generated by one of the followings:
- special rules to create a combination of characters or numbers ex) card numbers
- UUID
- input value ex) username, email address
- serial numbers
Value Object
A value object is an immutable object defined by attributes, not identity. The use of value object can improve the readibility in the code.
For example, let's say there is a NewsArticle class. It may look like:
public class NewsArticle
{
private string authorName;
private string authorEmailAddress;
private string title;
private string body;
//...
}
In this class, AuthorName
field and AuthorEmailAddress
field can be grouped together. These fields belongs to Author
. Now we can write another class:
public class Author
{
private string name;
private string emailAddress;
public Author(string name, string emailAddress)
{
this.name = name;
this.emailAddress = emailAddress;
}
public string GetName()
{
return this.name;
}
public string GetEmailAddress()
{
return this.emailAddress;
}
}
A value object doesn't have to be a set of fields. A value object can be used to give the code a clearer meaning. For example, instead of string, we can give the fields more characteristics by naming Title
and Content
.
public class NewsArticle
{
private Author author;
private Title title;
private Content body;
//...
}
We can also add functionalities to a value object. However, it should not provide a method to set a field. This is to guarantee referential transparency in the code.
public class Title
{
private string value;
//...
//getValue()
public Title Capitalize(Title title)
{
//..
return new Title(char.ToUpper(title[0]) + title.Substring(1));
}
}
Aggregate
An aggregate is a collection of entities and value objects that are conceptually related. Imagine there is an ERD diagram includes hundreds of tables. This introduces confusion in identifying the relationships between core domain concepts. We need a more high level diagram to do this. To make a domain more a manageable and comprehensive unit, we use an aggregate. It also provides the consistency boundaries and invariants enforcement. It reduces the complexities in a domain using encapsulation.
Each aggregate has a root entity that encapsulates the children entities in the aggregate. It controls the access to its children entities. In the above example, the root of Order Aggregate
is Order
entity.
Factory
When the creation of an aggregate or an entity involves too many relationships or exposes the internal structure, we can delegate the responsibilty to create an object to factory. It encapsulates the logic that is required in the creation of an object with consistency. It reduces the complexities when creating a domain object.
Repository
It is a logical storage of aggregates. It defines the interfaces to store or query domain objects. It manages data access from persistent storage. It enables a quick access to aggregates and CRUD operations over aggregates. There should be one repository per aggregate root.
Domain Service
A domain service performs domain logic that do not fit into an aggregate. This is different to an application service. The application service doesn't have domain knowledge. It receives requests from a user and calls a domain service. Additionally, it calls infrastructure service such as sending a message.
The operations in the domain service is typically stateless. The service does not need to be instantiated. It can be used by passing parameters to a method.
Domain Event
A domain event handles the events happened in the domain. This is used to exchange data between bounded contexts and to keep the consistency between aggregates.
Bounded Context
A bounded context is a logical boundary in a domain so we can focus on a specific domain model. It is independent from other bounded contexts.
Context Map
A context map provides the overview of the whole structure of the system. It visualises the relationships among bounded contexts.