How to Successfully Land DDD: A Practical Engineering Walkthrough
This article walks through the author’s two‑year experience applying Domain‑Driven Design to payment, settlement and fund services, explains strategic vs tactical design, compares classic DDD layers, Clean, Hexagonal and Diamond architectures, details module organization, CQRS, and shares practical pros, cons and implementation tips.
Hello, I’m Jensen, a programmer who enjoys tackling challenges together with the community.
Over the past two years I have applied DDD to payment, settlement, and fund services in my company, receiving positive feedback while also facing skepticism. After months of research I now share the practical experience of bringing DDD to production.
0x1 Engineering Architecture Layering Theory
DDD consists of strategic design and tactical design. Domain modeling belongs to the strategic layer, while DDD engineering implementation belongs to the tactical layer. They can be combined as needed; for example, a traditional MVC architecture can still use DDD for domain modeling, and the DDD architecture works best when domain modeling is done first.
1. Classic DDD Four‑Layer Architecture
Interface → application | domain | infrastructure
application → domain | infrastructure
domain → infrastructure
The layers serve the following purposes:
User Interface / Presentation: displays and interprets user commands.
Application: defines tasks the software must perform and coordinates domain objects; it contains no business knowledge.
Domain / Model: core of the system, expresses business concepts, state, and rules; focuses on domain object analysis (entities, value objects, aggregates, domain services, events, repositories, factories).
Infrastructure: provides persistence for domain models and generic technical support (messaging, utilities, configuration).
2. Clean Architecture
Proposed by Robert C. Martin in 2012, Clean Architecture uses concentric circles to represent software layers. The outer circle is tactical (implementation), the inner circle is core principles. Dependencies must point inward; inner layers know nothing about outer layers, and outer data formats should not be used inside.
This rule ensures that when external modules change (e.g., replacing a legacy database), inner layers remain untouched.
3. Hexagonal Architecture
Hexagonal Architecture (Port‑Adapter) splits the system into an internal hexagon (business logic) and an external hexagon (driving logic, infrastructure, other applications). The internal side communicates with the outside via ports (defined protocols) presented as APIs. One port can map to multiple external systems, each requiring an adapter to translate protocols, enabling consistent driving by users, programs, tests, or scripts while keeping the core isolated from actual devices and databases.
4. Diamond Architecture
The diamond architecture, applied to bounded contexts, merges ideas from DDD layered architecture and Hexagonal Architecture. It creates a symmetric inner‑outer structure centered on the domain. The inner side focuses on domain models, while the outer gateway layer is divided into north‑bound gateways (local controllers, remote APIs) and south‑bound gateways (port abstractions, adapter implementations).
5. CQRS
Command Query Responsibility Segregation separates write commands from read queries. Created by Greg Young in 2010 and based on Bertrand Meyer’s CQS pattern, CQRS introduces distinct APIs for state‑changing commands and for retrieving state information.
0x2 Engineering Architecture Layered Design
Combining the strengths of various architectures, we built a custom stack suited to our company’s situation:
Use the classic DDD four‑layer skeleton as the backbone, guided by other good architecture ideas.
Apply CQRS in the DDD application layer to handle complex operations and queries.
Apply Clean Architecture between the domain and infrastructure layers, separating interfaces from implementations.
Use Diamond Architecture to keep the domain model central, with north‑bound gateways (local controllers, remote APIs) and south‑bound gateways (port abstractions, adapter implementations).
Our Base framework encapsulates basic CRUD interfaces in the dal package, acting as a glue between domain and infrastructure, simplifying linkage.
Advantages: business‑technology decoupling, easier iteration, clearer knowledge accumulation, and higher reusability. Drawbacks: requires strong business analysis skills, more layers increase design effort, higher learning curve, and longer design time.
0x3 Engineering Code Construction Case
Before looking at code, we examine domain modeling. The internal transaction center is divided into five aggregates: internal transfer, rule center, internal inbound/outbound, internal sales, and internal procurement. Each aggregate forms a bounded context, which guides microservice boundaries.
We map the DDD architecture diagram to code organization. Using Maven, the project is split into two top‑level modules: api and service.
API Module
Acts as a cross‑service contract; all service layers may depend on it.
Defines enums, constants, request/response DTOs, and API interfaces. The Feign layer does not perform business segmentation.
Contains only contracts, no business logic, to avoid unnecessary upgrades when business changes.
Service Module
Holds the actual implementation; the DDD four‑layer architecture lives here.
The Application entry point shares the same directory as the DDD layers.
An alternative is to split service into four independent modules (api, application, domain, infrastructure) to enforce dependency rules via Maven, but this reduces flexibility and adds weight.
1. Access Layer (api)
Thin layer that directly handles front‑end requests or Feign facades, performs data conversion (assembler), and defines request/response contracts.
Controllers perform pre‑validation; business logic is delegated to application or domain services.
Business segmentation is not evident at this layer; controllers are grouped by front‑end modules. Complex business often crosses domain boundaries, so no further sub‑packages are created under the facade.
Assemblers handle complex data conversion; simple conversions may call utility methods directly.
2. Application Layer (application)
Responsible for business orchestration, forwarding, validation, handling cross‑aggregate logic and domain events; complex operations/queries are also placed here (CQRS).
AppService provides simple logic encapsulation; the access layer cannot directly obtain results from the domain layer, so it delegates to AppService.
Application layer may depend on the domain layer but not on the access layer; parameters are either primitive types or converted by the assembler.
Events, usually cross‑aggregate or cross‑service, are defined and processed in this layer.
Access layer can call domain layer directly, bypassing the application layer when appropriate.
3. Domain Layer (domain)
Core of the system, expresses business concepts, state, and rules; contains no technical code.
Aggregates are named after business concepts; each aggregate contains models (DO objects), repository interfaces, and domain services.
Domain models (named XxxDO) follow a “poor‑man” model: only atomic operations, no persistence logic.
Repositories are defined as interfaces; implementations reside in the infrastructure layer.
Domain factories (different from GoF factories) handle complex object construction and are placed at the same level as aggregates.
External APIs and framework code are shallowly wrapped in an external package, named ExXxxService, with implementations in infrastructure to act as anti‑corruption layers.
4. Infrastructure Layer (infrastructure)
Contains technical, non‑business code: frameworks, utilities, configuration, as well as repository implementations and external service adapters.
Repository implementations fulfill the domain‑defined interfaces; DAO classes and database entities ( XxxPO or XxxEntity) live here.
Param classes, used for query parameters, are placed here due to their close relation to PO objects, even though this slightly violates strict DIP.
Following Clean Architecture, all framework‑specific code stays in this layer, allowing business code to remain stable when technical stacks change.
0x4 Difficult Analysis
1. Rich vs. Anemic vs. Other Models
Anemic (pure getters/setters) – rarely used.
Anemic model – contains attributes, getters/setters, and non‑persistent atomic domain logic; persistence lives in the service layer.
Rich model – adds persistence operations and most business logic; instantiation may bring unnecessary related models.
“Bloated” model – only domain objects and DAO, encapsulating transactions at the domain level.
Based on Spring and personal experience, a plain (anemic) model is more appropriate for our code base.
2. Application Service vs. Domain Service
Application Service (application layer): coordinates between presentation and domain, orchestrates tasks, handles larger‑grain operations and transaction management.
Domain Service (domain layer): expresses core business concepts and rules, contains fine‑grained, highly reusable logic, does not manage transactions.
The key difficulty is recognizing which code belongs to business logic; accurate domain understanding is essential.
3. Classification of Special Code
Factory pattern – used for creating domain objects, placed in domain factories or services.
Strategy pattern – defines a strategy interface with multiple implementations, often in domain or application services.
Observer pattern – implemented via Spring events to decouple code.
Chain of Responsibility – splits complex logic into a chain of handlers, typically in domain or application services.
Principle: place core logic in the layer where it naturally belongs to avoid scattering.
0x5 Some Experience
DDD domain modeling three steps: define boundaries, create a ubiquitous language, and organize the model.
DDD engineering implementation four steps: integrate architectural ideas, decide partitioning strategy, map models to code, and classify special code.
In summary, successful DDD implementation hinges on clear business boundary identification and decoupling business from technical code. Before writing code, decide where each piece belongs, combine proven architectural concepts with the current technical stack, and avoid forcing DDD where it does not fit.
Architect's Journey
E‑commerce, SaaS, AI architect; DDD enthusiast; SKILL enthusiast
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
