Implementing Domain Driven Design

Implementing Domain Driven Design

Implementing Domain Driven Design

Overall

Domain-Driven Design (DDD) is an approach to software development that focuses on the development of programming a domain model that has a rich understanding of the processes and logic of a domain. The term is referred from a 2003 book by Eric Evans that depicts the approach through a list of patterns.

Today’s contents:

  • Domains, SubDomains
  • Bounded Context
  • Context Maps
  • Architecture
  • Entites
  • Value Objects
  • Aggregates
  • Repositories
  • Factories
  • Services
  • Integrating Bouded Context
  • Example

Notes:

  • Almost all the knowledge I'm going to share here is from the book "Implementing Domain-Driven Design" by Vaughn Vernon, it provides steps to implement DDD with Java code examples, and it helps you get ideas and apply them to other languages.
  • I also took the reference when applying DDD using Go language from https://threedots.tech/tags/domain-driven-design/

Domain

What's a Domain Model

A software model of the very specific business domain you’re working on, which:
  • Is often implemented as object models
  • Has both data and behaviors
Domains and Subdomains have both a problem space and a solution space:
  • The problem space enables us to think of a strategic business challenge to be solved.
  • The solution space focuses on how we will implement the software to solve the problem of the business challenge.

Why you should do DDD

  • The design is the code, and the code is the design.
  • It provides software development technicals that address both strategic design and tactical design.

How to do DDD

  • Ubiquitous Language
    • Is a shared team language
    • Language shared by domain experts and developers (developed by the team)
  • Bounded Context
    • As a conceptual boundary around a whole application
    • Every use of a given domain term, phrase, or sentence - the Ubiquitous Language - inside the boundary has a specific contextual meaning.
  • There is one Ubiquitous Language per Bounded Context

Strategic Design

Strategic design helps us understand the most important software investments to make, existing software assets to leverage in order to get there fastest and safest, and who must be involved.

Domain and Subdomains

Domain

A Domain, in the broad sense, is what an organization does and the world it does in it
  • Can refer to both the entire domain of the business, as well as just one core or supporting area of it
    • CORE DOMAIN
    • SUB DOMAIN

Bounded Context

What's Bounded Context

  • A specific solution, a realization view used to realize a solution as software
  • Is Explicit and Linguistic (Boundary)
    • Is an explicit boundary within which a domain model is exists
    • Inside the boundary all terms and phrases of the Ubiquitous Language have specific meanings and the model reflects the Language with exactness
  • Models are developed in a Bounded Context
  • A Bounded Context is a logical and physical boundary around a coherent and consistent domain or subdomain

In the figure above we have 8 Physical System

Context Map

Purpose

  • Illustrates how the actual software-bounded contexts in the solution space are related to one another through integration
  • What is the relationship between these Bounded Contexts
    • Partnership
    • Shared Kernel
    • Customer-Supplier Development
    • Conformist
    • Anticorruption Layer: As a downstream client, create an isolating layer to provide your system with functionality of the upstream system in terms of your own domain model. This layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in one or both directions as necessary between the two models
    • Open Host Service: Define a protocol that gives access to your subsystem as a set of services. Open the protocol so that all who need to integrate with you can use it
    • Published Language: The translation between the models of two Bounded Contexts requires a common language. Use a well-documented shared language that can express the necessary domain information as a common medium of communication, translating as necessary into and out of that language. Published Language is often combined with Open Host Service
    • Separate Ways
    • Big Ball of Mud
  • For detail explaination of Partnership, Shared Kernel, Customer-Supplier Development, Conformist, Separate Ways, Big Ball of Mud can read more in the reference book I listed at the ending of the article


Abbreviations

  • U: Upstream
  • D: Downstream
  • OHS: Open Host Service
  • PL: Published Language
  • ACL: Anticorruption Layer


IDP and Digitial Wallet

  •  Login


Architecture

Big Advantages of DDD

  • Doesn’t require the use of any specific architecture
  • Use just the right choices and combinations of architecture and architectural patterns

Layer Architecture

Hexagonal or Ports and Adapters

A Combination of Many Architectures


Tactical Design

  • How to implement the solution by code
  • OOP good practices

Terms

  • Entities
  • Value Objects
  • Aggregates
  • Repositories
  • Factories
  • Services

Entities

Mutable Identifiable Structs
  • When we care about individuality
  • Mutable (change state)

Here we model WalletDetail as a Domain Entity so we make it mutable, in the method WithAuditInfo we allow it to change its state (it's different with Value Object which you can see in the section below). With WalletDetails we also define as []*WalletDetail instead of []WalletDetail

Value Objects

Immutable Unidentifiable Structs

  • No Identifier
  • Measures, Quantifies, or Describes a thing in Domain
  • Can be maintained as Immutable
  • Models a Conceptual Whole by composing related attributes as an integral unit
  • Completely replaceable when the measurement or description changes
  • Can be compared with others using Value equality
  • Supplied its collaborators with Side-Effect-Free Behavior
In the example above we see that WithUpdateUserID method, we see that we clone a new object before editing. This is how we apply the rule "Completely replaceable when the measurement or description changes". It makes value objects immutable

Persisting Value Objects

Probably most times that a Value Object is persisted to a data store (for example, using an ORM tool along with a relational database) it is stored in a denormalized fashion; that is, its attributes are stored in the same database table row as its parent Entity object.
 
There are times, however, when a Value Object in the model will of necessity be stored as an Entity with respect to a relational persistence store, when persisted, an instance of a specific Value Object type will occupy its own row in a relational database table that exists specifically for its type, and it will have its own database primary key column. This happens, for example when supporting a collection of Value Object instances with ORM. In such cases, the Value type persistent data is modeled as a database entity.
 
There are some ways to store Value Objects as below
  • Single Value Objects
    • Store each of the attributes of the Value in separate columns of the row where its parent Entity is stored. Said another way, a single Value Object is denormalized into its parent Entity’s row
  • Many Values Serialized into a Single Column
    • There are some potential drawbacks to consider:
      • Column Width
      • Must query: If any of the Value attributes must be queryable, you may not use this option (nowadays MySQL and other DB types can support JSON match)
  • Many Values Backed by a Database Entity
    • Treat the Value type as an entity in the data model, as a noted reiteration, it's persisted as an entity in the data model does not mean it should be presented as an Entity in the domain model - Design your data model for the sake of your domain model, not your domain model for the sake of your data model.
    • The database's primary key will be implemented in storage persistence and hidden from the domain model.
  • Many Values Backed by a Join Table (In the example section when implementing the data persistence for Tenant-Member-Roles I use this approach)

Aggregates

The combined set of Entities and Value objects, stored in Repositories
  • An Aggregate is composed of either a single Entity or a cluster of Entities and Value Objects that must remain transactionally consistent throughout the Aggregate's lifetime
  • Unique Identifier by Root Entity (Aggregate root)
  • Is synonymous with transactional consistency boundary
  • A properly designed Aggregate is one that can be modified in any way required by the business with its invariants completely consistent within a single transaction
  • A properly designed Bounded Context modifies only one Aggregate instance per transaction in all cases
  • Is persisted using it's Repository

Rules

  • Model True Invariants in Consistency Boundaries
    • An invariant is a business rule they must always be consistent
  • Design Small Aggregates
  • Reference Other Aggregates by Identity
  • Use Eventual Consistency Outside the Boundary

Reasons to Break the Rules

  • User Interface Convenience
  • Lack of Technical Mechanisms
  • Global Transactions
  • Query Performance
  • ...

Repositories

  • An implementation of storing aggregates or other information
    • Store and manage aggregates
  • 1-1 relationship between an Aggregate type and a Repository
    • However, sometimes when two or more Aggregate types share an object hierarchy, the types may share a single Repository
  • Designs
    • Collection-oriented design
    • Persistence-oriented design

Factories

A constructor to create complex objects and make creating new instances easier for the developers of other domains
  • Creating complex, value objects, entities, aggregates

Services

A collection of repositories and sub-services that builds together the business flow

INTEGRATING BOUNDED CONTEXT

  • Using RESTful Resource
  • Using Messaging
  • .....


Example

Recognize Domain & Sub Domain

Recognize Bounded Context

Recognize Context Map

  •  Simple Context Map

  • Zoom in on Context map

Tactical Design

Note: Code example provided using Go language

As we said before, Tactical Design helps us to implement the solution by code and apply OOP good practices. At this step, we need to determine which objects are Aggregates, Entities, and Value Objects. In DDD the advice is to try to determine more Value Objects more values will you get. A Value Object can also map to a physical table not only Domain Entity, don't try to model an object as a domain entity just because it's persisted in data model as a database entity, the reason to make decision should be Domain Entity or Value Object depends on its characteristics in business domain model.

The Data Model Should Be Subordinate

Design your data model for the sake of your domain model, not your domain model for the sake of your data model.

1st example: let's walk to the first example of a User in the system. As you can see in the Login diagram we model a User as Aggregate (Root) along with Value Objects: InternalUserIdentifier, ExternalUserIdentifier, Email, UserToken. Whenever we need to access an Entity, Value Object requires us to always access via AggergateRoot (User). This is the rule in DDD we should keep in mind, and to access an Aggregate we need to provide a repository:


- User (Aggregate) <---> UserRepository (Repository)

2nd example: let's model a concept of a wallet group that has many wallet details. In this business requirement maybe we will have 2 approaches:

1st approach:  If every time we add/update a wallet group, it requires us to add/update the group's wallet details as well then we should design WalletGroup as an Aggregate Root and Wallet Details as Entities (or Value Objects - it depends on the business if there is no unique identifier required). As a result:

  • Aggregate: Wallet Group
  • Entities: Wallet Detail
  • Value Objects: Group Info, Apply Info, Approval Info, ...
  • Repositories: WalletGroupRepo

2nd approach: if the operation on the wallet group and wallet detail don't happen at the same time, eg: after creating a wallet group then we will add wallet details to the wallet group later (another intuitive example is: after a Product Owner creates a User Story then developers will add Tasks to a User Story later) or

We can break the rule of converting Wallet Detail from Entity to Aggregate if there are many Wallet Details in a Wallet Group and it causes a heavy load, or we need to support pagination, ... In reality, there is an existing case that allows us to break the rule (but keep in mind only break the rule if there is no better choice)
 
As a result:
  • Aggregate: Wallet Group, Wallet Detail
  • Value Objects: Group Info, Apply Info, Approval Info, ...
  • Repositories: WalletGroupRepo, WalletDetailRepo

3rd example: let's model the concept of a tenant's member and a member having some roles.

Developers who don't know Domain Driven Design will think about table design first and mapping from table to entity and DAO to access these entities. The following tables will come out: members, roles, member_roles, and provide MemberRepo, RoleRepo, MemberRoleRepo, (or MemberDAO, RoleDao, MemberRoleDAO) to access these data
 
But if we follow DDD then maybe we only need MemberRepo
  • Aggregates: Member
  • Value Objects: Display Name, Email Address, Roles
  • Repositories: MemberRepo

And the physical tables behind

We don't see the repositories of roles, tenant_member_roles (Repository != DAO - Data Access Object)

Notes:

Inside the Domain & Application Layer we won’t see any usage of primitive data type in a direct way: string, integer, … except for boolean value (but rarely)

  • The issue of passing the wrong order of params goes away due to strong type-checking
    • Func func1(a int, b int)
      • var a int =1
      • var b int =1
      • We can not detect we pass wrong order: func1(a, b) and func1(b,a)
    • Func func2(a INTA, b INTB)
      • type INTA int
      • type INTB int
      • With custom type we can detect the wrong order of pass in a & b variable:
         func2(a, b) and func2(b,a)
  •  Contribute to the Rich Model
    • We can add more business logic on user-defined type
Domain Driven Design approach helps us build the code to stick to business domain and make the concept model become a rich model

Reference


https://martinfowler.com/bliki/DomainDrivenDesign.html

https://threedots.tech/tags/domain-driven-design/

More like this

Is Code Review as important as Salary Review?
Oct 31, 2023

Is Code Review as important as Salary Review?