☰
DDD领域驱动设计实战指南
2026/10/15 4:02:04 网站建设 项目流程

DDD 领域驱动设计实战指南

DDD(Domain-Driven Design)是复杂业务系统的架构方法论,也是 CSDN 架构板块的高频话题。当业务逻辑复杂到"贫血模型 + 一堆 Service"写不下去时,DDD 是正解。本文从核心概念讲起,覆盖限界上下文、聚合、实体/值对象、仓储、领域服务、防腐层、落地分层与代码示例。


一、DDD 是什么

1.1 解决的问题

传统 MVC 开发的经典病:

Controller → Service(全是 if/else 和 SQL)→ Mapper

贫血模型:实体只有 getter/setter,业务逻辑全堆在 Service——业务越复杂,Service 越长,谁都不敢改。

DDD 的思路:让业务逻辑回到领域模型里——先梳理业务(领域),再定技术实现。

1.2 核心思想

  • 以业务为中心:技术细节(数据库、MQ)是配角;
  • 统一语言:业务方和开发用同一套术语(订单、支付、库存);
  • 模型驱动:代码结构反映业务结构。

1.3 DDD vs 传统分层

维度传统(贫血)DDD(充血)
业务逻辑位置Service 层领域模型(实体/聚合)
关注点数据库/接口业务规则
代码结构按技术分层按业务模块(限界上下文)
变更影响Service 越来越大影响收敛在聚合内

适用:业务复杂、规则多、迭代快的中大型系统;简单 CRUD 项目用 DDD 是过度设计。


二、战略设计(划边界)

2.1 限界上下文(Bounded Context)

概念:一个业务边界,边界内使用统一的领域模型和语言。

例子(电商):

订单上下文(下单、改单、状态流转) 支付上下文(支付、退款、对账) 库存上下文(库存增减、锁定) 商品上下文(SPU/SKU、上下架)

关键:同一"订单"在不同上下文含义不同——订单上下文里的订单带金额状态,物流上下文里的订单是发货单。不要全局共享一个 Order 类。

2.2 上下文映射(Context Mapping)

关系说明场景
共享内核两个上下文共享部分模型紧密协作
防腐层(ACL)隔离外部系统,翻译模型对接老系统/第三方
客户方-供应方上游提供能力下游消费常规依赖
分离方式完全独立弱关联

2.3 防腐层(Anti-Corruption Layer)

问题:外部系统(老系统、第三方)的模型和我们的领域模型不一致,直接依赖会污染领域。

解决:加一层翻译:

我们的领域模型 ← 防腐层(适配器翻译)← 外部系统
// 外部 DTO(对方系统)publicclassThirdPartyOrderDto{publicStringoid;publicStringstatusCode;}// 领域模型(我们内部)publicclassOrder{publicOrderIdid;publicOrderStatusstatus;}// 防腐层适配器:翻译@ComponentpublicclassThirdPartyOrderAdapter{publicOrdertoDomain(ThirdPartyOrderDtodto){returnOrder.rebuild(OrderId.of(dto.oid),OrderStatus.fromCode(dto.statusCode));}}

三、战术设计(建模要素)

3.1 实体(Entity)

有唯一标识、有生命周期、状态会变的对象。

publicclassOrder{privateOrderIdid;// 唯一标识(值对象)privateOrderStatusstatus;// 状态会变privateMoneytotalAmount;privateList<OrderItem>items;// 业务行为(不是 getter/setter 堆砌)publicvoidpay(Moneyamount){if(status!=OrderStatus.PENDING_PAY){thrownewDomainException("订单状态不允许支付");}if(!totalAmount.equals(amount)){thrownewDomainException("支付金额不匹配");}this.status=OrderStatus.PAID;}publicvoidcancel(){if(status==OrderStatus.SHIPPED){thrownewDomainException("已发货订单不能取消");}this.status=OrderStatus.CANCELLED;}}

核心:业务规则写在实体里,而不是 Service 里。

3.2 值对象(Value Object)

没有身份、由属性值定义、不可变的对象。

// 金额:值对象(不可变)publicfinalclassMoney{privatefinalBigDecimalamount;privatefinalCurrencycurrency;privateMoney(BigDecimalamount,Currencycurrency){this.amount=amount;this.currency=currency;}publicstaticMoneyof(BigDecimalamount,Currencycurrency){if(amount==null||amount.compareTo(BigDecimal.ZERO)<0){thrownewIllegalArgumentException("金额非法");}returnnewMoney(amount,currency);}publicMoneyadd(Moneyother){// 相同币种才能相加returnMoney.of(this.amount.add(other.amount),this.currency);}// 没有 setter——不可变}

识别:两个值对象属性相同就相等(比较值);实体靠 id 相等(比较身份)。

3.3 聚合与聚合根(Aggregate)

聚合:一组实体和值对象的集合,作为一个整体操作;聚合根是入口。

例子(订单聚合):

Order(聚合根) ├── OrderItem(实体) ├── Money(值对象) └── OrderId(值对象)

规则:

  • 外部只能通过聚合根访问(不能直接操作 OrderItem);
  • 事务边界 = 聚合边界(一个聚合一个事务);
  • 聚合要小:一个聚合别装太多实体(订单和用户不该在一个聚合)。
publicclassOrder{// 聚合根:对外只暴露操作聚合的方法publicvoidaddItem(ProductIdproductId,Moneyprice,intcount){// 校验 + 修改内部 OrderItem}}

3.4 领域服务(Domain Service)

不适合放实体里的业务(跨聚合、跨实体的流程)放领域服务。

@ServicepublicclassOrderDomainService{publicvoidplaceOrder(Orderorder,StockServicestock){// 跨聚合协调:订单聚合 + 库存聚合order.validateForPlacement();stock.lockStock(order.getItems());// 库存上下文order.markPlaced();}}

3.5 仓储(Repository)

领域模型的"数据库接口"——领域层只依赖接口,不依赖 MyBatis/JPA。

// 领域层定义接口publicinterfaceOrderRepository{OrderfindById(OrderIdid);voidsave(Orderorder);}// 基础设施层实现@RepositorypublicclassOrderRepositoryImplimplementsOrderRepository{@AutowiredprivateOrderMapperorderMapper;@OverridepublicOrderfindById(OrderIdid){OrderDOdo=orderMapper.selectById(id.value());returnOrderMapper.convertToDomain(do);// 转换}}

为什么这样设计:领域层不关心数据库——换存储/换实现不动业务代码。


四、落地分层架构

4.1 经典 DDD 四层

接口层(Controller、DTO、组装) ↓ 应用层(Application Service:用例编排、事务、权限) ↓ 领域层(实体、值对象、聚合、领域服务、仓储接口)★核心 ↓ 基础设施层(Mapper、Redis、MQ、外部调用)

依赖规则:内层不依赖外层——领域层不 import Controller/Mapper。

4.2 代码目录

com.example.order/ ├── interfaces/ # 接口层 │ ├── OrderController.java │ └── dto/ # DTO(对外) ├── application/ # 应用层 │ ├── OrderApplicationService.java │ └── command/ # 入参命令对象 ├── domain/ # 领域层(核心) │ ├── model/ # 实体/值对象/聚合 │ ├── service/ # 领域服务 │ └── repository/ # 仓储接口 └── infrastructure/ # 基础设施层 ├── mapper/ # MyBatis ├── repository/ # 仓储实现 └── external/ # 外部调用

4.3 应用层 vs 领域层(高频混淆)

应用层(Application)领域层(Domain)
用例编排(先做什么后做什么)业务规则(能不能做、怎么做)
事务、权限、事务边界领域不变量
调领域服务/仓储实体行为、领域服务
例:placeOrder 流程控制例:订单状态机校验

判断标准:这条规则离开业务系统就不成立 → 领域层;这条规则是系统流程 → 应用层。


五、实战:订单创建 DDD 落地

5.1 领域模型

// 聚合根:OrderpublicclassOrder{privatefinalOrderIdid;privatefinalCustomerIdcustomerId;privateOrderStatusstatus;privateMoneytotalAmount;privatefinalList<OrderItem>items=newArrayList<>();privateOrder(CustomerIdcustomerId){this.id=OrderId.generate();this.customerId=customerId;this.status=OrderStatus.CREATED;this.totalAmount=Money.zero();}publicstaticOrdercreate(CustomerIdcustomerId){returnnewOrder(customerId);}publicvoidaddItem(ProductIdproductId,StringproductName,Moneyprice,intcount){// 业务规则:数量必须为正if(count<=0)thrownewDomainException("数量必须大于0");items.add(newOrderItem(productId,productName,price,count));this.totalAmount=this.totalAmount.add(price.multiply(count));}publicvoidconfirm(){if(items.isEmpty())thrownewDomainException("订单没有商品");this.status=OrderStatus.CONFIRMED;}}

5.2 应用服务

@ServicepublicclassOrderApplicationService{@AutowiredprivateOrderRepositoryorderRepository;@AutowiredprivateStockDomainServicestockDomainService;@TransactionalpublicOrderIdcreateOrder(CreateOrderCommandcmd){// 1. 创建订单(领域逻辑)Orderorder=Order.create(CustomerId.of(cmd.getCustomerId()));for(ItemCmditem:cmd.getItems()){order.addItem(ProductId.of(item.getProductId()),item.getProductName(),Money.of(item.getPrice(),"CNY"),item.getCount());}order.confirm();// 2. 锁库存(另一个聚合的领域服务)stockDomainService.lock(order.getItems());// 3. 保存(仓储)orderRepository.save(order);returnorder.getId();}}

5.3 接口层

@RestController@RequestMapping("/orders")publicclassOrderController{@AutowiredprivateOrderApplicationServiceorderAppService;@PostMappingpublicOrderVOcreate(@RequestBodyCreateOrderRequestreq){OrderIdid=orderAppService.createOrder(CreateOrderCommand.from(req));returnnewOrderVO(id.value());}}

分层干净:Controller 只做参数转换,不碰业务。


六、DDD 常见问题与坑

问题原因解决
实体还是一堆 getter/setter没把规则放进去把校验/状态流转写进实体
Service 仍然巨长应用层和领域层混了流程留应用层,规则下放领域
聚合太大一个聚合装整个业务拆小聚合,跨聚合用领域服务
和数据库模型强耦合直接暴露 DO仓储转换,领域与 DO 隔离
简单项目过度设计滥用 DDD按业务复杂度取舍
事务乱聚合边界不清一个聚合一个事务

本章小结

DDD 的落地要点:战略上划清限界上下文,战术上把业务规则放回领域模型。判断做没做对:实体里有没有业务逻辑?领域层有没有依赖 Mapper?应用层是不是只剩编排?三个都是"是",DDD 就落地了。记住——DDD 解决的是业务复杂度,不是技术复杂度。

下一篇讲 Linux Shell 脚本——运维自动化的基础功。

(全文完)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询