自Eric Evans提出领域驱动设计(Domain-Driven Design,简称DDD)以来,这一方法论逐渐成为应对复杂业务系统的重要指导思想。它不仅仅是一种技术架构模式,更代表了一种以业务为核心、围绕真实世界问题建模的软件设计理念。在当前微服务与分布式架构盛行的背景下,理解DDD相较于传统分层架构的优势及其落地方式,对于构建高内聚、低耦合且具备长期演进能力的系统具有重要意义。
1. DDD的核心理念:为何需要它?
DDD的诞生源于一个深刻认知:
系统的复杂性主要不来自技术实现,而是源自业务领域本身的错综复杂。
当业务规则频繁变更、逻辑交织难解时,传统的数据表驱动或简单分层架构往往会导致业务逻辑分散于Service层、数据库存储过程甚至前端校验中,最终形成难以维护的“大泥球”式代码结构。随着时间推移,代码与实际业务语义渐行渐远。
为解决此问题,DDD主张建立一套
统一的领域模型,作为开发人员与业务专家之间的共同语言(即“通用语言”),并将该模型作为整个系统设计的基础。所有技术实现——包括数据库设计、框架选型和用户界面——都应服务于这个模型,而非主导其形态。
为了清晰展现DDD与传统架构的本质区别,以下从多个维度进行对比分析:
| 维度 | 传统分层架构(如MVC、经典三层) | 领域驱动设计(DDD) | 核心差异解析 |
|---|---|---|---|
| 设计驱动力 | 数据与技术。通常以数据库表结构为起点,逐层向上构建CRUD接口。 | 业务与领域。从真实的业务概念、流程和规则出发,向下映射到技术实现。 | DDD是业务驱动,而传统架构多为数据/技术驱动。 |
| 核心关注点 | 聚焦于数据的增删改查(CRUD)操作以及各技术层职责的分离。 | 强调领域模型的完整性、业务规则的显性表达及领域逻辑的高度内聚。 | DDD追求的是业务逻辑的纯粹性和集中管理,传统架构则侧重于技术层面的职责划分。 |
| 业务逻辑位置 | 逻辑常被分散在Service层、数据库触发器、存储过程甚至前端代码中。 | 高度集中于领域层中的实体、值对象和领域服务之中。 | DDD通过将业务逻辑收拢至领域层,实现了对复杂性的根本性控制。 |
| 与数据库关系 | 模型与数据库强绑定,常表现为数据库表的直接映射(贫血模型)。 | 通过仓储(Repository)模式实现解耦,领域模型独立于持久化机制。 | DDD利用仓储抽象屏蔽持久化细节,实现领域与基础设施的松耦合。 |
| 应对复杂性的方式 | 依赖更细粒度的技术分层或引入新框架来缓解混乱。 | 采用限界上下文(Bounded Context)对大型领域进行战略拆分,降低认知负担。 | DDD提供了战略性分解工具,支持从业务边界角度划分系统。 |
| 适用场景 | 适用于业务逻辑简单、以数据管理为主的内部系统或原型项目。 | 特别适合业务复杂、需长期迭代演进的核心商业系统。 | DDD在复杂系统中价值突出,在简单场景下可能造成过度设计。 |
2. DDD的四层架构详解
经典的DDD采用垂直分层结构,划分为四个清晰层次,每层有明确职责并遵循严格的依赖方向,确保领域核心不受外部技术细节干扰。
- src
- it 集成测试模块
- java 集成测试代码
- resources 集成测试配置文件
- test 单元测试模块
- java 单元测试代码
- main 业务代码
- java
- interfaces 用户接口层
- facade 提供较粗粒度的调用接口,将用户请求委托给一个或多个应用服务进行处理
- rest REST API
- dubbo
- subscribe mq 事件订阅
注1:统一返回Result
注2:应该捕捉所有异常
- application 应用层
- assembler 实现 DTO 与领域对象之间的相互转换和数据交换
- event 存放事件相关代码,为了事件统一管理,将所有事件发布和订阅统一放到应用层,核心业务逻辑放到领域层
- publish 事件发布
- service 对领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度服务
- command 操作相关,必须调用领域层
- query 只放查询相关,可以直接调用持久层
注1:出参必须为 DTO
注2:入参为 Command 或 Query,唯一例外是单ID查询的场景
注3:Command 和 Query 有语义作用,避免复用
注4:通过 Spring Validation 来实现入参校验,减少非业务代码混杂业务代码中
- domain 领域层
- aggregate 聚合目录,按业务名称命名,如权限聚合
- entity 领域对象
- factory 从其他服务返回结果构建领域对象???
- valueobject
- event 存放事件实体和相关的业务逻辑代码
- service 存放领域服务代码
- repository 仓储,存放所有查询和持久化领域对象的代码,通常包括仓储接口和实现,仓储实现一般放在基础层,也可以直接放一起
- infrastructure 基础层
- config 存放配置相关代码
- client 存放跨服务接口、DTO
- service
- dto 存放 dto
- command
- query
- response
- common 存放消息、数据库、缓存、文件、总线、网关、公用的常量、枚举等
- enums 存放枚举
- cache 缓存相关服务
- mq mq相关配置
- db 数据库相关
- mapper 存放 mybatis dao 实现
- repositories 仓储实现
- po 持久化对象
- converter 用于封装底层,实现PO与DO一对多或多对多转换
- perisistence 存放 RepositoryImpl,调用 mapper
- ......
- util 存放平台、开发框架、第三方类库、通用算法等基础代码
- resources 配置文件
典型工程目录结构如下所示:
变更追踪示例:订单与其明细对象中,仅修改某一条明细项,其余数据保持不变。
场景:非聚合根实体变更操作
修改订单明细中其中一条价格,这条明细和订单总价需要变动,其他信息不变
持久化到数据库时,只需要修改 orderItem_x 条记录和 order 记录即可
贫血模式:
会记录对应的item id ,根据这个ID 更新
充血模式:
需要自己做对比,变更跟踪
解决方案:(待测试)
aggregate-persistence
各层职责深度剖析
用户界面层(Interface/API Layer)
职责:负责向终端用户或外部系统展示信息,并接收输入指令。本层不包含任何业务逻辑。
关键组件:
- Controller / REST API:接收请求,解析参数,调用应用服务,并返回DTO结果。
- DTO(Data Transfer Object):专用于接口传输的扁平化数据结构,与内部领域模型隔离。
- ViewModel:将领域对象转换为适合前端展示的数据格式。
示例:Web应用的控制器、移动端UI组件、RESTful接口等。
代码示例:
java// Spring Boot Controller示例 @RestController @RequestMapping("/orders") public class OrderController { private final OrderApplicationService appService; @PostMapping public ResponseEntity<OrderDTO> createOrder(@RequestBody CreateOrderRequest request) { // 1. 参数基本校验(如非空) // 2. 调用应用服务,传入纯数据参数 OrderDTO orderDTO = appService.createOrder(request.getUserId(), request.getItems()); // 3. 返回给前端的DTO return ResponseEntity.ok(orderDTO); } }
应用层(Application Layer)
职责:协调领域对象完成具体的用户用例(User Case)。如同乐队指挥,本身不执行具体演奏(即不包含核心业务规则),只负责流程调度。
关键组件:
- 应用服务(Application Service):提供细粒度的服务方法,通常每个方法对应一个用户操作。
- 用例协调:组合多个领域对象或调用领域服务,处理事务边界和安全认证等横切关注点。
- 事务管理:保证跨多个领域操作的原子性(例如使用Spring的声明式事务)。
- 权限校验:验证用户身份与访问权限(如JWT鉴权)。
示例:用户注册流程、订单支付流程等典型业务场景。
代码示例:
java@Service @Transactional // 事务管理在此层 public class OrderApplicationServiceImpl implements OrderApplicationService { private final OrderRepository orderRepository; private final InventoryService inventoryService; // 领域服务 @Override public OrderDTO createOrder(String userId, List<OrderItemRequest> items) { // 1. 协调流程:检查库存(调用领域服务) if (!inventoryService.isSufficient(items)) { throw new InsufficientInventoryException(); } // 2. 创建领域对象(工厂模式) Order order = Order.create(userId, items); // 3. 调用领域对象行为(此时业务逻辑在Order实体内部) order.confirm(); // 4. 持久化(调用基础设施层的仓储接口) orderRepository.save(order); // 5. 发布领域事件(可选,用于跨限界上下文通信) domainEventPublisher.publish(new OrderConfirmedEvent(order.getId())); // 6. 返回组装好的DTO return OrderAssembler.toDTO(order); } }
@Transactional
领域层(Domain Layer)
职责:作为DDD的心脏部分,承载全部业务逻辑,体现业务领域的核心概念、规则、状态变迁与行为流程。
关键组件:
- 实体(Entity):具有唯一标识且生命周期可追踪的对象,其状态随时间变化(如
Order、User)。 - 值对象(Value Object):描述某种属性特征但无独立身份的对象,通常不可变(如
Address、Money)。 - 聚合(Aggregate) & 聚合根(Aggregate Root):一组具有强一致性的关联对象集合,外部只能通过聚合根访问内部成员,确保业务一致性。
- 领域服务(Domain Service):当某个操作涉及多个实体或无法归属于单一对象时,由领域服务封装该逻辑。
- 领域事件(Domain Event):表示领域中已发生的重要事实,可用于触发后续动作或实现事件溯源。
Order
User
Money
Address3、DDD(领域驱动设计)专用术语
1. 通用语言(Ubiquitous Language)
定义:开发、产品与业务人员共同使用的统一术语体系,用于消除沟通中的歧义,确保各方对业务概念的理解一致。
示例:在电商系统中使用“购物车”而非代码化的“CartDTO”,用“优惠券”代替技术性表述“PromotionCode”。
2. 限界上下文(Bounded Context)
定义:将复杂的系统划分为多个具有明确边界的业务区域,每个区域内拥有独立的模型和术语。
示例:一个电商平台可被拆分为“用户上下文”、“订单上下文”、“支付上下文”等独立模块,各自维护自身的逻辑与数据结构。
3. 聚合根(Aggregate Root)
定义:作为聚合的入口点,负责维护其内部实体的一致性和完整性,所有外部操作必须通过聚合根进行。
示例:
Order作为聚合根,统一管理
OrderItem和
Payment等关联子实体,保证事务边界内的数据一致性。
4. 防腐层(Anti-Corruption Layer, ACL)
定义:在不同限界上下文交互时,防止外部系统的模型污染本地上下文的领域模型,起到翻译与隔离作用。
示例:将第三方支付平台返回的数据结构转换为内部标准化的
PaymentResult对象,避免直接依赖外部接口。
5. 上下文映射(Context Map)
定义:描述各个限界上下文之间的协作关系,如共享内核、客户-供应商模式或遵奉者模式等。
示例:用户上下文与订单上下文之间采用“共享内核”方式,共用
User这一核心模型,减少重复建模成本。
4、通用架构术语
1. 高内聚低耦合(High Cohesion, Low Coupling)
定义:模块内部功能高度相关,而模块之间依赖尽可能少,提升系统的可维护性与扩展性。
示例:领域层仅依赖基础设施层定义的抽象接口,而不绑定具体数据库实现。
2. 依赖倒置(Dependency Inversion)
定义:高层模块不应依赖低层模块,二者都应依赖于抽象;抽象不应依赖细节,细节应依赖抽象。
示例:应用服务调用仓储时依赖的是
UserRepository接口,而非具体的MySQL或MongoDB实现。
3. CQRS(命令查询职责分离)
定义:将写操作(Command)与读操作(Query)分离,使用不同的模型处理不同类型的请求。
示例:创建订单走完整的领域模型流程,而查询订单信息则从缓存或专用读模型中获取,提高性能。
4. 事件溯源(Event Sourcing)
定义:不直接保存当前状态,而是通过一系列不可变的事件来重建对象的状态。
示例:订单的最终状态由
OrderCreatedEvent、
OrderPaidEvent等多个事件依次回放还原而成。
5. 最终一致性(Eventual Consistency)
定义:系统允许短时间内存在数据不一致,但经过一段时间后所有副本将达到一致状态。
示例:在分布式环境下,用户完成支付后,库存扣减可能略有延迟,属于典型最终一致性场景。
4. 战略设计与战术设计:DDD的两大支柱
4.1 战略设计:划分问题空间
战略设计聚焦于从业务角度对系统进行高层次划分,其核心工具是限界上下文(Bounded Context)。例如,在一个大型电商系统中,可以将其分解为“订单上下文”、“库存上下文”、“支付上下文”以及“物流上下文”。每个上下文拥有独立的通用语言和领域模型,并通过上下文映射机制(如防腐层ACL、事件发布/订阅)实现集成。这种划分方式也是确定微服务边界的关键依据。
4.2 战术设计:构建解决方案
战术设计是在已划定的限界上下文中,运用实体、值对象、聚合、领域服务等模式,逐步构建出精细且可落地的领域模型。它是将战略设计转化为实际代码结构的核心过程,决定了领域逻辑能否被正确表达和长期演进。
仓储接口 (Repository Interface)
定义:提供对领域对象持久化操作的标准定义,接口位于领域层,实现在基础设施层。
示例:商品价格策略、金融风控规则等复杂业务逻辑中,仓储用于加载和保存聚合根。
基础设施层 (Infrastructure Layer)
职责:为上层提供各类通用技术能力的支持,充当系统的“工具箱”,涵盖数据存储、通信、异步处理等功能。
关键组件:
- DAO/ORM 与仓储实现:基于JPA、Hibernate或MyBatis等框架实现数据访问逻辑,对接数据库。
OrderRepository - 消息中间件客户端:集成Kafka、RabbitMQ等实现异步通信,支持领域事件的发布与消费。
- HTTP Client:使用Feign、RestTemplate等工具调用外部API服务。
- 文件存储:支持AWS S3、本地磁盘等方式进行文件上传与管理。
- 缓存机制:引入Redis等缓存组件提升系统响应速度。
- 其他实现:包括MySQL数据库、SMTP邮件服务等底层技术支持。
代码示例:
java
@Repository
public class JpaOrderRepository implements OrderRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public Order findById(Long id) {
// 复杂情况下,这里需要组装聚合(从多张表查询,重建Order及所有OrderItem)
return entityManager.find(Order.class, id);
}
@Override
public void save(Order order) {
entityManager.persist(order);
// 通常会在这里或应用层事务提交后,处理order中暂存的领域事件
publishEvents(order);
}
}
代码示例(领域模型核心)
java
// 聚合根:订单
public class Order extends BaseAggregateRoot<Long> {
private Long id;
private String orderNumber;
private CustomerId customerId;
private OrderStatus status;
private Money totalAmount;
private List<OrderItem> items; // 内部实体列表,外部不可直接访问
// 【核心】静态工厂方法,封装创建逻辑
public static Order create(String userId, List<OrderItemRequest> itemRequests) {
Order order = new Order();
order.id = OrderIdGenerator.nextId();
order.orderNumber = generateOrderNumber();
order.customerId = new CustomerId(userId);
order.status = OrderStatus.CREATED;
order.items = itemRequests.stream()
.map(req -> new OrderItem(req.getProductId(), req.getQuantity(), req.getPrice()))
.collect(Collectors.toList());
order.calculateTotal(); // 内部方法计算总价
return order;
}
// 【核心】实体行为方法,封装状态变更规则
public void confirm() {
if (this.status != OrderStatus.CREATED) {
throw new IllegalOrderStateException("Only CREATED orders can be confirmed.");
}
this.status = OrderStatus.CONFIRMED;
// 记录领域事件
this.registerEvent(new OrderConfirmedEvent(this.id, LocalDateTime.now()));
}
// 值对象:订单项
@Getter
public static class OrderItem {
private final ProductId productId;
private final Integer quantity;
private final Money price;
private final Money subTotal; // 通过构造器计算,确保不变性
public OrderItem(ProductId productId, Integer quantity, Money price) {
this.productId = productId;
this.quantity = quantity;
this.price = price;
this.subTotal = price.multiply(quantity); // 业务规则内聚
}
}
}
5. DDD的应用场景与决策
适用场景:
- 业务逻辑高度复杂:当系统涉及大量规则判断、状态流转和校验流程时,DDD有助于理清逻辑层次。
- 业务需要长期演进:面对频繁变更的需求和大型团队协作,清晰的领域模型能有效降低维护成本。
- 核心域项目:对于企业核心竞争力相关的系统,投入较高设计成本以换取长期可维护性和扩展性是值得的。
需谨慎或避免的场景:
- 简单CRUD管理后台:若仅为基础增删改查功能,引入DDD会增加不必要的复杂度。
- 一次性或短期项目:开发周期短、无长期维护需求的项目,采用轻量级架构更为合适。
- 技术探索型项目:在业务边界尚不清晰的情况下,应优先验证技术可行性,再考虑引入DDD。
6. 现代架构中的DDD应用
DDD的理念已深度融入当代主流软件架构之中:
- 微服务架构:限界上下文天然对应微服务的拆分边界,成为服务粒度划分的重要理论支撑。
- 事件驱动架构:领域事件作为跨上下文或微服务间通信的桥梁,促进松耦合、高扩展性的系统设计。
CQRS(命令查询职责分离)是一种常与领域驱动设计(DDD)结合使用的架构模式。在该模式中,命令端负责处理写操作,依赖完整的领域模型来保障业务一致性;而查询端则专注于读操作,采用经过优化的视图模型以提升查询性能。两者之间通过事件机制实现数据同步。在业务逻辑复杂的系统中,这种分离有效缓解了领域模型复杂性与高并发查询性能之间的冲突。

六边形架构与整洁架构是体现“领域核心独立”思想的典型代表,其设计理念与DDD的分层结构高度契合。这类架构强调将业务逻辑置于应用中心,外部依赖如数据库、UI或第三方服务作为可插拔的外围组件,因而常被用作实现DDD的技术骨架,支撑领域模型的纯粹性与可维护性。
- src
- it 集成测试模块
- java 集成测试代码
- resources 集成测试配置文件
- test 单元测试模块
- java 单元测试代码
- main 业务代码
- java
- interfaces 用户接口层
- facade 提供较粗粒度的调用接口,将用户请求委托给一个或多个应用服务进行处理
- rest REST API
- dubbo
- subscribe mq 事件订阅
注1:统一返回Result
注2:应该捕捉所有异常
- application 应用层
- assembler 实现 DTO 与领域对象之间的相互转换和数据交换
- event 存放事件相关代码,为了事件统一管理,将所有事件发布和订阅统一放到应用层,核心业务逻辑放到领域层
- publish 事件发布
- service 对领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度服务
- command 操作相关,必须调用领域层
- query 只放查询相关,可以直接调用持久层
注1:出参必须为 DTO
注2:入参为 Command 或 Query,唯一例外是单ID查询的场景
注3:Command 和 Query 有语义作用,避免复用
注4:通过 Spring Validation 来实现入参校验,减少非业务代码混杂业务代码中
- domain 领域层
- aggregate 聚合目录,按业务名称命名,如权限聚合
- entity 领域对象
- factory 从其他服务返回结果构建领域对象???
- valueobject
- event 存放事件实体和相关的业务逻辑代码
- service 存放领域服务代码
- repository 仓储,存放所有查询和持久化领域对象的代码,通常包括仓储接口和实现,仓储实现一般放在基础层,也可以直接放一起
- infrastructure 基础层
- config 存放配置相关代码
- client 存放跨服务接口、DTO
- service
- dto 存放 dto
- command
- query
- response
- common 存放消息、数据库、缓存、文件、总线、网关、公用的常量、枚举等
- enums 存放枚举
- cache 缓存相关服务
- mq mq相关配置
- db 数据库相关
- mapper 存放 mybatis dao 实现
- repositories 仓储实现
- po 持久化对象
- converter 用于封装底层,实现PO与DO一对多或多对多转换
- perisistence 存放 RepositoryImpl,调用 mapper
- ......
- util 存放平台、开发框架、第三方类库、通用算法等基础代码
- resources 配置文件
总结:从技术落地到业务价值升华
领域驱动设计的本质是一场思维范式的转变——它推动团队从传统的“如何存储数据、如何展示页面”转向深入理解“业务本身的运作逻辑与核心规则”。借助一套结构化的模式语言,DDD帮助开发团队在错综复杂的业务环境中建立清晰、精准的模型坐标。
尽管DDD存在学习门槛较高、初期投入较大的挑战,但对于正处于数字化转型关键阶段、面临持续增长的业务复杂度的企业来说,采用DDD是一项具有战略意义的技术投资。它有助于构建出具备可持续演进能力、高质量且贴近业务本质的核心系统。
成功实施DDD的关键在于两方面:一是业务专家与技术团队之间的深度协作,确保模型真实反映业务现实;二是对领域模型进行持续迭代和精细化打磨的耐心与坚持。最终,这种努力将转化为软件系统真正的优势——成为业务敏捷发展的助推器,而非制约其发展的技术负担。


雷达卡


京公网安备 11010802022788号







