Loading...
...
Posted by Firuza Polad on June 28, 2025

Domain Driven Design (DDD)

DDD is not a design pattern, not an architecture pattern/style, not a technology, coding technique, it is a software development approach that indicate close collaboration between development team and business people. We apply DDD when we have BL complexity and application features changes often. We don't apply when we have simple domain, CRUD based application, 20-30 use cases. Key Aspects:

  • - Centralized BS logic : core idea of DDD focus on Business domain itself, this ensure that software main purpose is solving business problems.
  • - Learn, discuss, bring BS value : continuous communication between them, developers and BS people help to each other to understand logic well, and achieve better design, where minimize gaps. Everyone use same language (UL) when discussing in system.
  • - Design is code, code is design : there is no strict separation between design and code. Design should be reflected in code, and code need represent the business design.
  • -Separation of Concerns : DDD encourage seperating Domain logic from Application/Infrastructure/UI logic. Often implemented with Clean Architecture or Onion Architecture. Note that, Clean and Onion architecture is a software architecture pattern , while DDD is a design and modeling approach.
  • - Strategic/Tactical designs: in DDD it brings both, Strategic and Tactical designs together.

1. Strategic design in DDD focus on higher level design that are help splitting the domain into manageable pieces.

  • ~ Subdomain is a business concept, we use it in Problem space. Simply, we can say it's a spesific part of business domain. Let’s say we are building an online bookstore that are selling books online, this is a whole business domain, but this domain has many parts which are callled subdomains, such as Catalog,Ordering,Billing,UserManagment etc. Each Subdomain is implemented by one or more Bounded Contexts, such as Catalog with CatalogBoundedContext, Ordering with OrderManagementBoundedContext etc. There are 3 types of domain:
    • a. Core Subdomain: is most important, unique part of business. Require deep collaboration with domain experts.
    • b. Supporting Subdomain: is not unique, used to reuse patterns or tools if possible. Eg; User managment.
    • c. Generic Subdomain: is commonly used logic that every company needs. We don't build it unless necessary. Instead using existing frameworks/libraries etc. Eg; Authentication & Authorization, Logging, Payment gateways etc.

  • ~ Ubiquitous Language(UL) is a shared language used by both the development team and business people to ensure clear communication. It is created by developers, domain expers and business experts. s. The goal is to define key terms that everyone agrees on and to visually represent domain elements and their relationships using diagrams or models. Terms like “Book,” “Order,” “Customer,” “Discount” are defined and used between business and developers. The UL is only valid on inside a given BC. Each BC has its own UL, and this language can't use across in other contexts, this ensure that language remains clear for given context.

  • ~ Bounded Context (BC) is a solution space where we can define and apply domain models independently. It isolates part of system to solve a specific part of larger problem. In practice, a BC often used with Microservices architecture, where each microservices has its own isolated context, responsibility, and often its own DB and architecture style. As earlier I mentioned, the UL is only valid with its BC, each BC has its own UL, even if multiple BC use similar terminology — like an "Account" in the Billing Context and in the UserManagement Context — the meaning of the term may be completely different within each other. Tactical pattern are implemented in BC. And each BC handle its own solution. BC related to Solution space not Problem space.

  • ~ Context Map (CM) provide high level overview to indicate how different BC are related to each other. This is crucial when mapping out integration relationships between multiple BC. There are some relationships between different part of system, ensuring they can work together without conflict, like "Partnership"- two BC are dependent on each other and must either success or fail together

  • ~ Shared Kernel is set of common domain models (enties, value objects, servcies) that are shared between two or more BC. It allows context to share some core business logic without completely merging their domains. Eg: in E-commerce app, two teams are working on two Bounded Context: OrderManagment and CustomerService, but both context need to use Customer data (Customer entity, Address value object), instead of each team defining their own version of Customer, they share same domain model of Customer and Address. So this part is Shared Kernel. Since multiple context depend on Shared Kernel, changes to it require careful coordination between teams to avoid breaking other parts of the system.


2. Tactical design in DDD focus on code-level construct, such as value object, entity, aggregate (root) ,model, repositories, domain services and application services etc. This pattern is used to implement a Domain Model in a BC. To apply Tactical Patterns effectively, we should : first define BC, then create UL, and ensure clear solution space communication using CM, then we can apply Tactical Pattern.

  • ~ Entity: is a class that represent business concept with unique identifier even if their attribute has same name. This is not an Anemic model - a class only consist of properties and logic declared somewhere else (in a service layer), It is anti-pattern in DDD, violtes rules. Entity class tied to Domain.
    a. Identified by its unique ID: It differentiate one entity from another;
    b. Has a lifecycle: can be created,modified,deleted;
    c. Mutable and stateful: represented by its attributes, which can change over time
    Two entities are only considered equal, if they have the same identity, but usually a unique identifier assigned to an entity when it’s created. We use Equals() and GetHashCode() for equality check.

    - Equals() - defined in System.Object class and inherited by all objects in C#. Works for both value types and reference types: For value types checks the content/value. For reference types it behaves like ReferenceEqual() method, means check if both references point to the same object. But unlike ReferenceEqual(), we can override Equals() for reference types.
    Note: == and Equals() both check content for Value types, but for Reference types we can override == by impelenting operator overloading. When we override Equals() we must also override GetHashCode(), if we override == and != then we should also override Equals() and GetHashCode() to keep behavior.
    - GetHashCode() - provide hash code (int) for an object, used in hash-based collections (Dictionaries, HashSet) for quickly find and group objects that might be equal. If two objects are equal based on Equals() then they must return the same hash code.

  • ~ Model : Is a simple data representation, may or may not have ID, typically used outside of strict DDD boundariers. Often used to holding data only, not behavior. Stateless and not responsible for BS rules. Can be Mutable and Immutable based on its use case. Tied to ViewModel , DTO, InputModel and used to carry data between layers.

  • ~ Value object (VO): It is a concept in the domain, doesn't have an Identity, described by its attributes. If two value objects are equal then all their properties are equal. To prevent duplicates, we need logic to compare all fields. We create class named ValueObject and override Equals(), GetHashCode(). Without it, Sets or LINQ won't behaves as excepted. It is Immutable, means once created, then can not be changed later, instead create new instance. Has no lifecycle, commonly used in Entity.

  • ~ Aggregate & AggregateRoot(AR) : Aggregate is designed to encapsulate a group of related Enties and Value objects as single unit. Aggregate can contain multiple enties, but one entity/value object can be part of one aggregate. All objects are tied up together in aggregate.
    Aggregate Root (AR) is primary entity in Aggregate and control access to other entities. Because the other entities are private, they can be accessed via AR. Repositories typically works with AR.

  • ~ Repositories : Is abstraction over DAL layer and provide access to Aggregate. Act like a collection, provide methods Add(), Remove(), Update(), GetById() and return domain objects specifically Entities or Aggregate Roots, not DTO or Model. The Domain layer doesn't know where data comes from - DB, FIle, External service. Repository operate on AR only, not on internal entities and Interface declared in Domain layer , but implementation is provided in Infrastructure layer-ORM (EF,Dapper,Raw SQL).

  • ~ Domain services : Is used to encapsulate domain logic that doesn't belong to a single Entity. Contain BS rules and logic that are still part of domain model, but not tied to a spesific object. It is stateless, does not store data, only coordinates logic. Eg; CanPlaceOrder() encapsulate business logic if Customer can place Order. There are two enties, Customer and Order, but logic doesn't belong for either Customer or Order.
    NOTE: in here we don't use this method inside Entity, because Entity are handle logic belongs to their behavior, like change state of order, apply discount.

  • ~ Application services : Coordinates tasks in the application, such as calling domain services, interacting with repositories, handling transactions, logging, and communicating with external systems (UI, DB, APIs). In a nutshell, it handles the workflow of the application. Unlike Domain Services, it contains no business logic.

  • ~ Domain event : Something happens in domain that is important for BS logic. Once a domain event occurs, its state can not be changed. It typically describe something has already happened in the system, help keep different parts of application loosely coupled by allowing them to react to event without tight dependencies. Ex: when an order is placed, that could be Domain Event, because it may trigger other actions like sending a confirmation email, notify something.

  • ~ Modules : We can think as folder that group similar concepts together. Eg: Order Module have Order.cs, OrderPlacedEvent.cs etc.

  • ~ Factories : is responsible for creating complex objects (like Aggregates), help to ensure that obj are created follow the correct BS rules. Eg: we have Order aggregate that requires complex logic, like applying discount, and we return an Entity or Aggregate back in valid state.

Solution Space vs Problem Space: Solution space- is where we start define architecture (Microservices, Clean Architecture, CQRS) and technology (programming languages, frameworks, databases) that will be solve problems which is defined in Problem space, like BC is solution space, in here we implement Entities, Value Objects, Domain Services.
Problem space- explore and define BS problem, in here we focus on what business needs to achieve, what is workflow, and define the UL. It is often called Subdomain (distinct areas of the BS problem). First we define the Problem space by defining a UL, and then we map subdomains into one or more BC in Solution space. Eg; we define Book subdomain - what a Book means in business term, what workflow it has. Then we create Book BC-implement Book entity, reposiotry, domain service to solve BS problem.


Information
All diagrams, Folder structure, Code example in this blog were created by me using Lucid , Canva and IDE If you'd like to contribute, request more diagrams or text, or suggest improvements, feel free to contact me
© 2025 Firuza Polad. All rights reserved.

Let’s Connect

I’m open to collaboration, remote and freelance work!