Practical Experience and Technical Solutions for DDD, Clean Architecture, CQRS, and Saga in Backend Development
This article shares practical experiences and detailed technical solutions for applying Domain‑Driven Design, Clean (Onion) Architecture, CQRS, and Saga patterns in backend systems, covering theory recap, modeling methods, module separation, code examples, and distributed transaction strategies.
The article begins with an introduction that recounts the author’s journey from learning Domain‑Driven Design (DDD) to implementing three projects, summarizing the technical challenges encountered and the solutions adopted.
Theory Review provides a concise recap of core DDD concepts: domain, sub‑domain, bounded context, aggregates, aggregate roots, entities, value objects, and domain events, along with four design principles for aggregates.
Modeling Methods describe the use of event‑storming, user stories, and ubiquitous language to build a shared domain model, and mention UML class, use‑case, and sequence diagrams for visualizing the design.
Clean Architecture (Onion Architecture) outlines its four layers—Entities, Use Cases, Interface Adapters, and Frameworks & Drivers—explaining the dependency rule that outer layers may only depend inward.
Implementation Practices detail a kernel‑centric Maven multi‑module structure. The kernel (b2b‑baseproject‑kernel) contains common utilities, export APIs, DTOs, domain modules, read‑app and write‑app services, while the center (b2b‑baseproject‑center) provides SDKs, infrastructure, service, provider, and bootstrap modules. Example module listings are shown below:
内核Maven工程模块说明:
1. b2b-baseproject-kernel-common 常用工具类,常量等,不对外SDK暴露;
2. b2b-baseproject-kernel-export 内核对外暴露的信息,为常量,枚举等,可直接让外部SDK依赖并对外,减少通用知识重复定义(可选);
3. b2b-baseproject-kernel-dto 数据传输层,方便app层和domain层共享数据传输对象,不对外SDK暴露;
4. b2b-baseproject-kernel-ext-sdk 扩展点;(可选,不需要可直接移除)
5. b2b-baseproject-kernel-domain 领域层等(也可以不划分子模块,按需划分即可);
(b2b-baseproject-kernel-domain-common 通用领域,主要为一些通用值对象;
(b2b-baseproject-kernel-domain-ctxmain 核心领域模型,可自行调整名称;
6. b2b-baseproject-kernel-read-app 读服务应用层;(可选,不需要可直接移除)
7. b2b-baseproject-kernel-app 写服务应用层;To address cross‑module dependencies, a domain resource registry is introduced, allowing stateless services to be accessed from domain objects without Spring injection:
/**
* 租户注册中心
* @author david
* @date 12/12/22
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@Setter
public class TenantRegistry {
private TenantRepository tenantRepository;
private static TenantRegistry INSTANCE = new TenantRegistry();
public static TenantRegistry getInstance() { return INSTANCE; }
}A RecordLog value object records creation and update metadata, supporting optimistic locking and audit trails:
/**
* 日志值对象,用于记录数据日志信息
*/
@Getter
@Setter
@ToString
@ValueObject
public class RecordLog implements Serializable, RecordLogCompatible {
private String creator;
private String operator;
private Integer concurrentVersion;
private Date created;
private Date modified;
private transient boolean creating;
private transient boolean updating;
public static RecordLog buildWhenCreating(String creator) { ... }
public void update(String operator) { ... }
}The article also presents a Publisher interface that stores domain events in a weak‑hash‑map, enabling automatic cleanup when the publishing object is garbage‑collected:
public interface Publisher {
Map
> container = Collections.synchronizedMap(new WeakHashMap<>());
default void register(DomainEvent domainEvent) { ... }
default List
getEventList() { return container.get(this); }
// ... other methods
}CQRS and Read Service Design are illustrated with a query service interface and request/response DTOs, showing how read‑side models are kept separate from write‑side aggregates:
public interface TenantInfoQueryService {
TenantConstraint getTenantByCode(GetTenantByCodeReq req);
}
public class GetTenantByCodeReq implements Serializable, Verifiable {
private String tenantCode;
@Override public void validate() { Validate.notEmpty(tenantCode, CodeDetailEnum.TENANT); }
}The discussion then shifts to distributed transactions , focusing on the Saga pattern. It explains the original Saga theory, the two execution modes (choreography vs orchestration), and compares their advantages and drawbacks.
For choreography‑style Saga , each participant publishes events that trigger the next step, avoiding a central coordinator but risking circular dependencies. For orchestration‑style Saga , a central orchestrator issues commands and handles compensations, simplifying service interactions at the cost of a potential single point of failure.
An example of an invoice‑application Saga is described, showing how a master application event spawns two orchestrators: one for invoice creation and another for sub‑application processing, with diagrams (omitted here) illustrating the flow.
Finally, the article warns about the lack of isolation in Saga‑based systems, especially in financial domains, and suggests complementary techniques such as semantic locks, version checks, and idempotent operations.
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
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.