彻底吃透 DDD:领域驱动设计思想、分层架构、核心角色与实战落地
前言
在传统 CRUD MVC 架构项目中,随着业务不断迭代,极易出现业务逻辑散乱、Service 臃肿、状态流转混乱、微服务拆分无依据、代码与实际业务脱节等一系列问题。DDD 领域驱动设计正是为解决复杂业务系统架构乱象而生的一套业务建模与架构设计思想,并非具体开发框架,如今已成为中大型业务系统、微服务项目主流架构实践方案。 本文系统性梳理 DDD 核心思想、与传统 MVC 差异、四层架构划分、领域层四大核心角色定义,同时厘清应用层流程编排与领域层领域服务的核心边界,结合实战目录结构与调用链路,完成从理论到落地全梳理。
一、什么是 DDD 领域驱动设计
1. 核心定义
DDD(Domain-Driven Design)领域驱动设计,由 Eric Evans 提出,核心思想:以业务领域为核心驱动软件设计,摒弃数据库表驱动开发模式,让代码模型与真实线下业务模型完全对齐。
2. 核心设计理念
业务驱动技术,而非技术约束业务; 统一业务术语,产品、业务、开发、测试使用同一套领域语言; 通过限界上下文划分业务边界,实现业务解耦; 核心业务规则内聚收敛,杜绝业务逻辑散落各处; 依赖倒置设计,业务领域层不依赖任何底层技术框架。
3. DDD 主要解决的项目痛点
解决代码与业务脱节,业务语义无法体现在代码中; 解决复杂业务 if-else 泛滥、业务规则分散难以维护; 解决传统 MVC Service 层臃肿杂乱,职责划分模糊; 解决微服务拆分无标准,凭经验拆分导致循环依赖、边界混乱; 解决业务知识无法沉淀,新人接手项目难以梳理业务流程; 解决业务状态流转不受约束,线上业务异常频发。
二、DDD 架构 vs 传统 MVC 架构 核心差异
1. 架构设计核心
传统 MVC:面向技术分层,以请求流转、数据库 CRUD 为核心,先建数据表再编写业务代码; DDD 架构:面向业务领域分层,以业务建模、业务规则收敛为核心,先梳理业务领域再适配数据库。
2. 分层结构对比
传统 MVC 分层
Controller -> Service -> Dao -> DB
弊端:所有业务逻辑全部下沉至 Service 层,无业务边界划分,迭代极易形成屎山代码。
DDD 标准四层架构
接口层(HTTP入口) → 应用层(流程编排) → 领域层(核心业务) → 基础设施层(技术实现)
3. 主流实战融合方案
企业级最常用落地方式:DDD 四层架构 + MVC 接口层结合使用
外层保留 SpringMVC 原生 Controller 作为请求入口,沿用原有接口开发习惯;
内层严格遵循 DDD 四层架构划分职责,收敛所有核心业务逻辑;
兼顾开发效率与复杂业务架构规范性,无需重构原有 Web 接口体系。
4. 维度对比汇总
| 对比维度 | MVC 架构 | DDD 架构 |
|---|---|---|
| 设计驱动模式 | 数据表驱动 | 业务领域模型驱动 |
| 业务逻辑存放 | 分散多层,无统一收口 | 统一收敛至领域层 |
| 业务边界划分 | 依靠人为代码约定 | 限界上下文 + 聚合精准划分 |
| 流程与规则耦合 | 流程、业务规则混写 | 流程归应用层,规则归领域层 |
| 适用业务场景 | 简单CRUD、后台管理系统 | 复杂业务、金融、电商、微服务中台 |
| 代码依赖方向 | 上层强依赖下层 | 依赖倒置,领域不依赖底层技术 |
三、SpringBoot DDD 标准项目目录结构
可直接复制用于企业项目搭建
com.xxx.biz
├── controller # MVC 接口层:接收请求、参数校验、统一结果封装
│ └── dto # 前后端交互入参、出参实体
├── application # 应用层:纯流程编排,禁止编写任何业务规则
│ ├── service # 应用层业务编排服务
│ └── assembler # DTO 与 领域对象 转换器
├── domain # DDD 核心领域层(架构灵魂)
│ ├── order # 按限界上下文分包(订单域 / 用户域 / 库存域)
│ │ ├── entity # 普通领域实体
│ │ ├── aggregate # 聚合根
│ │ ├── valueobj # 值对象
│ │ ├── service # 领域服务
│ │ ├── event # 领域事件
│ │ ├── repository # 仓储抽象接口(只定义不实现)
│ │ └── enums # 领域状态枚举、业务枚举
├── infrastructure # 基础设施层:所有底层技术实现
│ ├── persistence # 数据库持久化实现
│ ├── cache # 缓存操作实现
│ ├── mq # 消息队列生产者 / 消费者
│ └── thirdparty # 第三方外部接口调用
├── common # 公共通用模块
│ ├── exception # 全局自定义异常
│ ├── util # 工具类
│ └── constant # 全局常量
└── config # 全局配置类
四、领域层四大核心角色完整详解
领域层承载系统所有核心业务规则,是 DDD 架构核心,包含四大核心组件: 值对象、领域实体、聚合根、领域服务
1. 值对象 Value Object
- 核心特征:无唯一业务ID、不可变,创建完成不允许修改,修改直接新建对象
- 核心职责:仅做业务属性描述,封装自身数据合法性校验
- 存在形式:无法独立存在,必须依附实体 / 聚合根使用
- 业务示例:收货地址、支付金额、手机号、商品规格、配送方式
2. 领域实体 Entity
- 核心特征:拥有唯一标识ID,具备完整业务生命周期,属性支持动态变更
- 核心职责:承载细分业务主体数据,封装自身简单业务行为、状态校验
- 业务示例:订单明细、商品SKU、用户角色、购物车条目
3. 聚合根 Aggregate Root
- 角色定位:特殊顶级领域实体,整个聚合对外唯一访问入口
- 核心作用:划定业务一致性边界、事务边界
- 开发强制约束:外部代码禁止直接操作聚合内部子实体,所有操作必须经由聚合根发起
- 核心职责:聚合整体创建、业务状态流转、聚合内部所有业务规则统一管控
- 业务示例:订单聚合根、用户聚合根、支付单聚合根
聚合结构关系示意图
聚合根 (Order 订单)
├─ 内部领域实体:OrderItem 订单明细
├─ 值对象:Address 收货地址
└─ 值对象:Money 订单金额
4. 领域服务 Domain Service
- 核心特征:无状态服务,不属于任何实体、任何聚合根
- 适用场景:跨多个聚合、跨多个业务实体的复杂联动业务
- 核心职责:封装纯领域业务规则、复杂业务计算、多聚合联动业务校验
- 编码强制约束:不操作数据库、不管理事务、不编排业务流程、无任何技术层代码
- 业务示例:下单库存校验、订单优惠券核销、账户转账金额校验
五、DDD 高频难点:应用服务 VS 领域服务
这是 DDD 落地最容易混淆的核心知识点,二者职责严格隔离,严禁越界编写代码。
1. 应用层服务 ApplicationService
- 角色定位:业务流程编排调度者
- 负责内容:定义业务执行先后顺序、事务控制、参数组装、对象转换、调用领域能力、推送领域事件
- 严格禁止:编写业务判断逻辑、业务规则校验、金额计算、状态合法性判断
- 特性:偏向技术层,可添加事务注解、对接缓存、MQ、第三方接口
2. 领域层服务 DomainService
- 角色定位:纯业务规则执行者
- 负责内容:编写行业固定业务规则、跨聚合业务校验、核心业务逻辑计算
- 严格禁止:编排业务执行流程、操作数据库、开启事务、对接前端请求
- 特性:纯业务代码,仅依赖领域实体与聚合根,零框架依赖、零底层技术依赖
3. 标准完整调用链路
MVC Controller 接口层
↓
应用层服务(编排执行步骤 + 事务控制)
↓
领域服务(执行跨聚合核心业务规则)
↓
聚合根 / 领域实体(执行自身内部业务行为)
↓
仓储抽象接口
↓
基础设施层(数据库持久化落地)
4. 一句话极简区分
- 应用层服务:只管 做事的先后顺序
- 领域层服务:只管 做事是否符合业务规则
六、DDD + MVC 极简代码示例(四层完整链路)
场景:创建订单 分层链路:Controller(MVC) → 应用层 → 领域层(聚合根) → 基础设施层(DB)
1. 公共依赖(异常/返回体)
// 自定义业务异常
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
// 统一返回体
public class R<T> {
private int code;
private String msg;
private T data;
public static <T> R<T> ok() {
R<T> r = new R<>();
r.setCode(200);
r.setMsg("success");
return r;
}
}
2. MVC 接口层(Controller + DTO)
2.1 请求 DTO
import lombok.Data;
import java.util.List;
@Data
public class OrderCreateReq {
private Long userId;
private String province;
private String city;
private String detail;
private List<OrderItemDTO> itemList;
@Data
public static class OrderItemDTO {
private Long goodsId;
private Integer num;
private BigDecimal price;
}
}
2.2 Controller
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
private final OrderAppService orderAppService;
public OrderController(OrderAppService orderAppService) {
this.orderAppService = orderAppService;
}
/**
* 只做:接收请求、参数校验、调用应用层、返回结果
*/
@PostMapping("/create")
public R createOrder(@RequestBody OrderCreateReq req) {
orderAppService.createOrder(req);
return R.ok();
}
}
3. 应用层(流程编排)
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class OrderAppService {
private final OrderRepository orderRepository;
public OrderAppService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
/**
* 应用层 = 纯流程编排,无任何业务规则
*/
public void createOrder(OrderCreateReq req) {
// 1. 组装订单明细
List<OrderItem> itemList = buildOrderItem(req);
// 2. 组装值对象(地址)
AddressVO address = AddressVO.create(req.getProvince(), req.getCity(), req.getDetail());
// 3. 调用聚合根创建订单(执行业务规则)
Order order = Order.create(req.getUserId(), itemList, address);
// 4. 仓储保存
orderRepository.save(order);
}
private List<OrderItem> buildOrderItem(OrderCreateReq req) {
return req.getItemList().stream().map(dto -> {
OrderItem item = new OrderItem();
item.setGoodsId(dto.getGoodsId());
item.setNum(dto.getNum());
item.setPrice(dto.getPrice());
return item;
}).collect(Collectors.toList());
}
}
4. 领域层(核心:值对象、实体、聚合根、仓储接口)
4.1 值对象(AddressVO)
import lombok.Data;
/**
* 值对象:无ID、不可变
*/
@Data
public class AddressVO {
private String province;
private String city;
private String detail;
private AddressVO() {}
public static AddressVO create(String province, String city, String detail) {
AddressVO vo = new AddressVO();
vo.setProvince(province);
vo.setCity(city);
vo.setDetail(detail);
return vo;
}
}
4.2 领域实体(OrderItem)
import lombok.Data;
import java.math.BigDecimal;
/**
* 领域实体(聚合内部子实体)
*/
@Data
public class OrderItem {
private Long itemId;
private Long goodsId;
private Integer num;
private BigDecimal price;
}
4.3 聚合根(Order)
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
/**
* 聚合根 = 订单(对外唯一入口、业务规则载体)
*/
@Data
public class Order {
private Long orderId;
private Long userId;
private String orderNo;
private Integer status;
private List<OrderItem> itemList;
private AddressVO address;
private Order() {}
/**
* 聚合根:创建订单 + 业务规则校验
*/
public static Order create(Long userId, List<OrderItem> itemList, AddressVO address) {
// 业务规则:订单明细不能为空
if (CollectionUtils.isEmpty(itemList)) {
throw new BusinessException("订单明细不能为空");
}
Order order = new Order();
order.setUserId(userId);
order.setOrderNo(UUID.randomUUID().toString().replace("-", ""));
order.setStatus(1); // 待支付
order.setItemList(itemList);
order.setAddress(address);
return order;
}
/**
* 聚合根:状态变更(支付成功)
*/
public void paySuccess() {
this.status = 2;
}
}
4.4 仓储接口(领域层定义)
/**
* 仓储接口:只定义,不实现(依赖倒置)
*/
public interface OrderRepository {
void save(Order order);
}
5. 基础设施层(仓储实现 + DB)
5.1 仓储实现
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Repository
public class OrderRepositoryImpl implements OrderRepository {
private final OrderMapper orderMapper;
private final OrderItemMapper orderItemMapper;
public OrderRepositoryImpl(OrderMapper orderMapper, OrderItemMapper orderItemMapper) {
this.orderMapper = orderMapper;
this.orderItemMapper = orderItemMapper;
}
/**
* 基础设施层:DB持久化实现
*/
@Override
@Transactional
public void save(Order order) {
// 1. 领域对象 → 数据库PO
OrderPO orderPO = OrderAssembler.toPO(order);
// 2. 保存主表
orderMapper.insert(orderPO);
// 3. 保存子表
List<OrderItemPO> itemPOList = OrderItemAssembler.toPOList(order.getOrderId(), order.getItemList());
orderItemMapper.batchInsert(itemPOList);
}
}
5.2 数据库 PO(简化)
import lombok.Data;
import java.math.BigDecimal;
// 订单PO
@Data
public class OrderPO {
private Long id;
private Long userId;
private String orderNo;
private Integer status;
}
// 订单明细PO
@Data
public class OrderItemPO {
private Long id;
private Long orderId;
private Long goodsId;
private Integer num;
private BigDecimal price;
}
5.3 Mapper(MyBatis)
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface OrderMapper {
@Insert("INSERT INTO `order`(user_id, order_no, status) VALUES(#{userId}, #{orderNo}, #{status})")
int insert(OrderPO orderPO);
}
public interface OrderItemMapper {
int batchInsert(@Param("list") List<OrderItemPO> list);
}
5.4 对象转换(Assembler)
public class OrderAssembler {
public static OrderPO toPO(Order order) {
OrderPO po = new OrderPO();
po.setUserId(order.getUserId());
po.setOrderNo(order.getOrderNo());
po.setStatus(order.getStatus());
return po;
}
}
public class OrderItemAssembler {
public static List<OrderItemPO> toPOList(Long orderId, List<OrderItem> itemList) {
return itemList.stream().map(item -> {
OrderItemPO po = new OrderItemPO();
po.setOrderId(orderId);
po.setGoodsId(item.getGoodsId());
po.setNum(item.getNum());
po.setPrice(item.getPrice());
return po;
}).toList();
}
}
六、DDD 开发强制落地编码规范
日常开发严格遵守以下规范,规避 90% DDD 错误写法
- 业务属性优先使用值对象承载,能不用实体尽量不用实体
- 聚合内部子实体禁止对外暴露,所有外部访问统一通过聚合根
- 简单自身业务行为编写在聚合根/实体中,跨聚合复杂逻辑抽离为领域服务
- 系统所有核心业务规则一律下沉至领域层,应用层只做调度编排
- 领域层仅定义仓储抽象接口,数据库 SQL 实现统一存放基础设施层
- 微服务拆分严格依据限界上下文划分,禁止按照数据库表拆分服务
七、全文总结
- DDD 不是用来替代 MVC,企业项目主流方案为
MVC接口层 + DDD四层架构平滑融合 - DDD 最大价值不在于编码格式,而在于业务梳理、边界划分、业务知识沉淀
- 吃透值对象、领域实体、聚合根、领域服务四大核心角色,即可掌握 DDD 80% 核心内容
- 严格划分应用服务与领域服务职责边界,是 DDD 项目长期稳定可维护的核心关键
- 轻量化简单 CRUD 项目无需强行接入 DDD,业务复杂度越高,DDD 架构收益越大