Fundamentals 23 min read

Unlock DDD: Practical Hexagonal Architecture and Repository Patterns

This article explores Domain‑Driven Design fundamentals, illustrating how to expose hidden concepts, adopt hexagonal architecture, and implement repository patterns to achieve high scalability, maintainability, and testability in complex software systems, while addressing common pitfalls like the anemic model.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
Unlock DDD: Practical Hexagonal Architecture and Repository Patterns

Software systems are delivered through development to solve specific business domains, and software design bridges business and development. Domain‑Driven Design (DDD) provides a mindset for designing large, complex software with high extensibility, maintainability, and testability.

Challenges of Applying DDD

DDD is a philosophy rather than a concrete technology, so there is no code‑level constraint. Because mature ORM frameworks (e.g., Hibernate, MyBatis) encourage direct database‑oriented development, many developers mistake DDD’s layered architecture for traditional layered architecture, leading to misunderstanding and difficulty in adoption.

1. Implicit Concepts Made Explicit

Example: A phone number consists of an area‑code and a number. Business logic often repeats validation and area‑code extraction across many methods. DDD suggests extracting the hidden concept "area‑code" into a value object

PhoneNumber

that encapsulates validation and extraction, eliminating repetitive code.

1.2 Implicit Context Made Explicit

Example: In a bank transfer, "1000" actually has two meanings: the numeric value and the currency unit (yuan). Ignoring the unit can cause bugs, especially for international transfers. DDD recommends creating a

Money

value object to make the currency context explicit.

1.3 Encapsulating Multi‑Object Behavior

Example: In cross‑border transfers, exchange‑rate conversion can be encapsulated in a value object, simplifying the method by moving calculation and validation logic into the object.

1.4 DP vs. Value Object

DP (Domin Primitive) is a concept introduced by Alibaba, extending the DDD value object with additional capabilities such as validation, independent behavior, and immutability (no side effects).

1.5 DP vs. DTO

DP vs DTO diagram
DP vs DTO diagram

1.6 Using DP vs. Not Using DP

DP usage diagram
DP usage diagram

2. Application Architecture under DDD

2.1 Standard DDD Application Architecture

Traditional MVC separates presentation, business logic, and data access layers, focusing on top‑down interaction. DDD splits the system into Application Layer, Domain Layer, and Infrastructure Layer, placing core business logic in the Domain Layer.

DDD layered architecture
DDD layered architecture

2.1.1 Application Layer

Coordinates interaction between the user interface and the domain layer; essentially orchestrates domain services without containing business logic.

2.1.2 Domain Layer

Implements core business logic, including entities, value objects, events, and domain services.

2.1.3 Infrastructure Layer

Provides technical capabilities such as databases, scheduled tasks, message queues, and gateways; contains no business logic.

2.2 Understanding Hexagonal Architecture

2.2.1 Re‑examining the Application Layer

External systems (e.g., XXL‑Job, MQ, HTTP/RPC interfaces) also call the service, so the application layer must coordinate external calls with the domain layer. To preserve testability, the application layer should depend on interfaces (dependency inversion) and abstract external services.

2.2.2 Re‑examining the Domain Layer

The domain layer should not depend on infrastructure; using repository interfaces abstracts persistence, allowing the domain layer to remain independent.

2.2.3 Re‑examining the Infrastructure Layer

Infrastructure provides ports and adapters: ports define external interactions, adapters implement data and concept conversion.

2.2.4 Evolution to Hexagonal Architecture

By inverting dependencies, the infrastructure layer becomes the outermost layer.

Hexagonal architecture diagram
Hexagonal architecture diagram

2.3 Placement of Utility and Configuration Classes

Utility classes (JSON parsers, string helpers) are logically part of the infrastructure layer, but to avoid breaking dependency order they can be placed in a separate "common" module. Configuration classes should be placed according to their nature: business‑related switches belong to the domain layer, while database configurations belong to the infrastructure layer.

2.4 Practical Hexagonal Architecture in a Project

Project hexagonal structure
Project hexagonal structure

3. Repository Pattern

3.1 What Is a Repository?

In DDD, a repository abstracts the data store, handling aggregate roots and belonging to the domain layer.

3.2 Why Use a Repository?

It decouples the domain from storage and provides a standard way to avoid the anemic model.

3.3 What Is the Anemic Model?

Entities contain only data mapped to database tables with little or no behavior; business logic is scattered across services, utilities, and handlers.

3.4 Detecting an Anemic Model

Entities (e.g., XxxDO) contain only fields without behavior.

Business logic is dispersed in services, controllers, utilities, helpers, handlers, leading to duplicated code.

3.5 Why the Anemic Model Is Bad

Cannot guarantee entity invariants; state can be altered arbitrarily.

Boundary of entity behavior is unclear, making changes error‑prone.

Strong coupling to persistence; changes in the database force changes in business code.

Software maintainability = amount of code to change when infrastructure changes. Software extensibility = amount of code to change when business logic changes. Software testability = execution time of test cases × number of test cases affected by a change.

3.6 Why the Anemic Model Persists

Developers often conflate data models with domain models, adopt a database‑first mindset, prioritize rapid delivery, and fall into script‑style coding, all of which reinforce the anemic approach.

3.7 Converting Data Models and Domain Models

After introducing repositories, data models and domain models are kept separate and converted via Assembler or Converter components.

3.8 Mapping Solutions

Dynamic mapping (BeanUtils, Dozer) uses reflection and incurs performance overhead. Static mapping (MapStruct) generates compile‑time code with performance equal to hand‑written mappings.

4. Domain Layer Design Guidelines

4.1 Entity Classes

Entities encapsulate state and behavior within a domain. Key principles:

Creation Consistency: Constructors must receive all required attributes or provide sensible defaults.

Use Factory pattern to simplify complex construction.

Avoid public setters; expose intent‑driven methods instead.

Aggregate roots enforce consistency among child entities.

Do not depend directly on other aggregates or domain services.

Depend on other aggregates only via IDs or method parameters.

Entity behavior should affect only itself and its children.

Prefer enums over inheritance for simple variations; consider Type Object pattern for data‑driven designs.

4.2 Domain Services

Used when a business operation requires multiple domain objects and returns a value object.

4.2.1 Single‑Object Strategy Service

Handles rules involving a single entity plus external dependencies.

4.2.2 Cross‑Object Transaction Service

Ensures consistency when a behavior modifies multiple entities.

4.2.3 Generic Component Service Provides reusable behavior not tied to a specific entity. Double Dispatch Example <code>public void equip(Weapon weapon, EquipmentService equipmentService) { if (equipmentService.canEquip(this, weapon)) { this.weaponId = weapon.getId(); } }</code> 4.3 Strategy Objects (Domain Policy) Encapsulate domain rules as stateless singleton objects with methods like canApply and a business operation. 4.4 Handling Side Effects – Domain Events Side effects (e.g., awarding points) are modeled as domain events propagated via an EventBus. The EventBus is typically a global singleton, which can hinder unit testing. 5. Conclusion Studying and practicing DDD deepens understanding of software design as a discipline that guides the construction of large, complex systems and provides a clear path for refactoring legacy codebases.

Software ArchitectureDomain-Driven DesignDDDHexagonal ArchitectureRepository PatternAnemic Model
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.