All you need to know about “Domain Driven Design” | by Karahan Ozen | Dec, 2023
Domain-Driven Design (DDD) is an approach to software development that centers on constructing software based on the requirements of business domains. It was first introduced by Eric Evans in his book; “Domain-Driven Design: Tackling Complexity in the Heart of Software” in 2003. As it turns out, It is not a new concept in software development community but, it has gained popularity with the increasing use of microservice architecture. Because, in microservices architecture, the primary objective is to decompose services into smaller, autonomous microservices, usually with the key criterion for their separation being their distinct business domains. So each team can be responsible for their own domain. Now that we’ve explored the reasons for the emergence of DDD, we can now learn more about the concept of it.
Why DDD Exists
DDD exists as a response to challanges faced in developing complex software applications, especially large systems that embeds lots of different business domains inside it. The successful development of such applications necessitates a profound understanding of business requirements. One of the significant challenges here is the communication gap between technical and non-technical stakeholders. Another big problem is requirements in complex domains are often ambiguous and prone to change. Developers are tasked with creating a system that can evolve over time, while adhering to essential programming best practices, including scalability, flexibility, and encapsulation of complexity. Developing an application of this magnitude requires a design approach capable of addressing these challenges, and that is precisely why DDD exists. Now let’s deep dive into concepts of DDD.
First, Domain-Driven Design (DDD) divides its concerns into two main aspects: Strategic Design and Tactical Design. These two aspects address different levels of abstraction and focus on distinct aspects of the software development process. Strategic Design focuses on defining the overall structure and organization of the software system based on the business domain (such as Domain, Ubiquitous Language, Bounded Contexts, etc.). On the other hand, Tactical Design concentrates on designing the internal structure of code within a bounded context to implement the domain model effectively (involving concepts like Entity, Value Object, Aggregate Root, etc.). We will delve into an explanation of these terms in the subsequent part of the article.
Domain
In Domain-Driven Design, the term “domain” refers to the subject area or problem space that the software system is being designed to address. For example, let’s consider an E-Commerce application. To operate this type of application, it is necessary to integrate various areas of expertise, including but not limited to Sales, Advertisement, and Customer Management. These areas require distinct types of expertise, each differing significantly. However, they must be orchestrated harmoniously into a unified system, which constitutes our E-Commerce application. The domain represents each of these specific area of expertise or business focus that the software is intended to support.
Domain Expert
In the previous example of an E-Commerce application, we defined some of our domains, such as Sales, Advertisement, and Customer Management. The developers responsible for the system’s development are not required to be experts in these domains. A Domain Expert is the person who possesses expertise in a specific business area.
Ubiquitous Language
Ironically, this seemingly hard-to-read term is alo about establishing effective communication. Ubiquitous Language is a fundamental pattern in DDD because DDD encourages developers to collaborate closely with Domain Experts. The reason is quite simple: to create a near-perfect system (acknowledging that no system is perfect), developers need to have a nearly perfect understanding of what they are developing. Hence, effective communication with Domain Experts is essential.
At first blush, the Ubiquitous Language seems like an obvious thing, and chances are a good that number of you are already practicing this intuitively. However, it is critical that developers use the business language in code consciously and as a disciplined rule. Doing so reduces the disconnect between business vocabulary and technological jargon.
It’s also important to use Ubiquitous Language in your code. For example, consider a microservices application. You can create a microservice for each domain or bounded context and write your entities using the same names as the business objects required in that domain. This practice not only fosters a shared understanding between developers and domain experts but also contributes to the overall cohesiveness of the system. Additionally, it aids in reducing cognitive load, making it easier for developers to navigate and comprehend the codebase, leading to more efficient collaboration and development processes.
Bounded Contexts
The Bounded Context serves as a central pattern in Domain-Driven Design, constituting the principal concept behind DDD’s strategic design for handling large systems. DDD deals with large systems by dividing them into small, distinct parts known as Bounded Contexts. While Bounded Contexts represent different models, it is possible that there are some interrelationships between them. These interrelationships need to be clearly defined.
In the diagram below, we can observe two bounded contexts within our E-Commerce application. While they exhibit distinct interests, there are shared commonalities between them (Customer and Sales for this example).
Defining Bounded Contexts can be challenging, as the boundaries between them are sometimes quite thin. When describing Bounded Contexts, It is worth noting that common words may have distinct meanings. For instance, the terms “Customer” and “Product” will be employed in both the Sales Bounded Context and the Customer Management Bounded Context; however, within each context, these terms represent different concepts. There are variety of ways to describe relationships between Bounded Contexts. It’s usually worthwhile to depict these using a context map.
Context Mapping
Context mapping is a method for visually describing a system’s Bounded Contexts. This approach can be particularly helpful, especially for distinguishing one context from another or identifying interrelationships among them.
In essence, the Context Map provides a high-level overview of the strategic design decisions made in the system, showcasing the relationships between different Bounded Contexts, such as partnerships, client-supplier dependencies, and shared kernels. It helps teams understand the context boundaries and facilitates effective communication among different parts of the organization, promoting a shared understanding of the system’s architecture.
Entities
Entity is a core concept in DDD, representing a distinct and identifiable object in the domain that is defined by its identity. Unlike value objects, entities have a distinct identity that runs through time and different states. In other words, two entities with the same attributes are still considered different if they have different identities.
Key characteristics of entities in DDD include:
- Identity: An entity is defined by a unique identity that distinguishes it from other entities, even if they have the same attributes.
- Lifecycle: Entities have a lifecycle that spans different states. They can be created, modified, and eventually deleted.
- Mutability: Entities can be mutable, meaning their state can change over time.
Here’s a simple example for our E-commerce application: consider a “Customer” entity in an Customer Management domain. Each customer has a unique identifier (identity), and even if two customers have the same name and address (attributes), they are still distinct entities because of their unique IDs. You can simply think that Customer can be a table in our SQL database since it is an entity.
In DDD, identifying and defining entities correctly is crucial for modeling the core business concepts and ensuring a rich understanding of the problem domain.
Value Objects
Value Object” is a concept used to represent a descriptive aspect of the domain with no conceptual identity. Unlike entities, which are defined by their unique identity, value objects are defined by their attributes. In other words, two value objects with the same attributes are considered equal, and they can be interchangeable.
Key characteristics of Value Objects in DDD include:
- Immutability: Value objects are typically immutable, meaning their state cannot be changed once they are created. This immutability ensures that the value object’s integrity is maintained.
- Equality based on Attributes: Two value objects are considered equal if all their attributes are equal. In other words, they are indistinguishable by their attributes.
- No Identity: Value objects do not have a distinct identity. They are identified by their attributes. For example, a money value object with an amount and currency has no unique identity beyond its amount and currency.
- Shared by Reference: Value objects are often shared by reference. If two entities or aggregates have the same value object, they can reference the same instance rather than duplicating the data.
To provide an example, consider a customer who ordered a product from our E-Commerce application. The customer’s payment can be a value object since only the amount paid is important here. You can replace an instance of the Payment
class with another one having the same amount attribute, and nothing will change.
public class Payment
public decimal Amount get; set;
Aggregate Root
An Aggregate Root can be conceptualized as a parent entity that serves as the orchestrator for a cluster of related sub-entities. It encapsulates the collective behavior and state of these entities, ensuring consistency and providing a single entry point for external interactions. The Aggregate Root manages the lifecycle of its internal entities, enforcing transactional consistency and maintaining a clear boundary within the domain model.
The aggregate root encapsulates a cluster of related entities and value objects. It is the only external access point to the aggregate, and external objects should interact with the aggregate only through the root. All actions pertaining to the aggregates are controled by the aggregate root.
For the E-Commerce application, consider an “Order” that serves as the aggregate root for Customer and Product entities, as well as Payment and Address value objects. The aggregate root is responsible for managing the lifecycle of its internal objects, controlling their creation, modification, and deletion within the aggregate.
public class Order : AggregateRoot
public Guid OrderId get; set; private Customer customer get; set;
private decimal paymentAmount get; set;
private string address get; set;
private List<Product> products get; set;
public Order(Customer customer, Payment payment, Address address, List<Products> products)
this.customer = customer;
this.paymentAmount = payment.Amount;
this.address = address.FullAddress;
this.products = products;
Services
In Domain-Driven Design (DDD), a “service” refers to a concept that represents an operation or behavior that doesn’t naturally fit within the boundaries of an entity or a value object. Services are stateless and are designed to perform specific actions within the domain. Services can be implemented as part of the application layer or as domain services within the domain layer. The choice depends on the nature of the service and whether it is more closely tied to the application’s infrastructure or the core domain logic.
public interface PaymentService
void ProcessPayment(Order order);
Services play a crucial role in keeping the domain model clean, focused, and aligned with the business requirements by encapsulating behavior that doesn’t naturally belong to entities or value objects all while fostering reusability.
Domain Events
In Domain-Driven Design (DDD), a “domain event” is a powerful concept that represents a notable change in the state of the system, often originating from the core domain. Domain events capture meaningful occurrences or state transitions within the business domain, and they are used to communicate these changes to other parts of the system. Domain events contribute to a loosely coupled architecture by allowing different parts of the system to react to changes without direct dependencies on one another by using the “publish-subscribe” pattern. This pattern facilitates a responsive and extensible system.
To give an example, if our e-commerce system receives a new order, a “OrderPlaced” domain event might be triggered, carrying information about the order. Various components, such as inventory management or order processing, can subscribe to this event and perform their respective actions. At the end of the day these domain events promotes scalability, maintainability, and flexibility to our application.
Finally
In conclusion, Domain-Driven Design serves as a guiding light in the intricate landscape of software development. By emphasizing a shared understanding of the business domain, employing a ubiquitous language, and structuring our code strategically and tactically in order to build and maintain complex systems.
Happy coding!
link