资讯动态

从理论到代码:一次完整的DDD领域建模与工程化实践

发布时间:2026/9/10 2:45:47 来源:尧图企业网站定制
1. 为什么我们需要DDD刚接触DDD领域驱动设计时我总觉得这是个高大上的概念直到接手了一个电商订单系统改造项目。当时系统已经迭代了三年代码量超过10万行新来的同事要看懂一个下单流程得花两周时间。最要命的是产品经理说优惠券开发理解成折扣码测试以为是促销码——同一个业务概念在不同角色嘴里变成了三个名字。这就是DDD要解决的核心问题在业务复杂度爆炸增长时如何让系统保持可理解、可维护。传统MVC架构在这种场景下会暴露出几个典型问题业务逻辑碎片化一个完整的优惠券使用逻辑可能分散在Controller、Service、Dao多个层级贫血模型泛滥订单对象变成只有get/set的数据容器业务规则以服务ifelse的形式寄生在Service层沟通成本剧增需求评审会上业务方说的库存和开发理解的inventory可能根本不是一回事我在重构订单系统时先用DDD的统一语言Ubiquitous Language方法拉着产品、运营、测试一起开了三天workshop。大家把业务术语都写在白板上最终确定优惠券统一叫Coupon库存专指可销售库存不包括预占库存订单特指主订单与子订单明确区分这个术语表后来成为我们团队的宪法新需求文档必须严格使用这些定义。三个月后需求误解率下降了70%这就是DDD的第一个魔法——用业务语言写代码。2. DDD的战术工具箱2.1 领域模型三剑客在技术落地时DDD提供了三种核心建模元素**实体Entity**就像你家的宠物狗——它有唯一ID狗牌号会成长变化从幼犬到成年但无论毛色怎么变ID始终不变。在代码中public class Order extends EntityLong { private OrderStatus status; private ListOrderItem items; public void cancel() { this.status OrderStatus.CANCELLED; this.addDomainEvent(new OrderCancelledEvent(this.id)); } }**值对象Value Object**则像你家的家具——不需要唯一标识完全由属性定义。换个新沙发只要款式一样对使用者没区别public class Address { private final String city; private final String street; // 没有ID字段 // 所有属性final确保不可变 }**聚合根Aggregate Root**是更高级别的封装比如订单聚合根会包含订单项、物流信息等子实体但外部只能通过订单根来修改内部状态// 正确做法通过聚合根操作 order.addItem(product, quantity); // 错误做法直接操作子实体 order.getItems().add(new Item(...)); // 破坏了封装性2.2 分层架构的进化传统三层架构在DDD中演进为更清晰的分工用户界面层 ↓ 应用层编排业务流程 ↓ 领域层核心业务逻辑 ↑ 基础设施层技术实现这个依赖关系的关键在于领域层不依赖任何其他层。比如订单支付逻辑应该纯粹用业务语言编写不关心支付方式是支付宝还是微信。我在项目中曾犯过错误把微信支付SDK直接引入领域服务结果后来换支付平台时不得不重写核心业务逻辑。正确的做法是通过依赖倒置// 领域层定义接口 public interface PaymentService { PaymentResult pay(Order order, Money amount); } // 基础设施层实现 public class WechatPaymentService implements PaymentService { // 具体对接微信API }3. 从事件风暴到代码3.1 建模实战电商订单履约去年双十一前我们用DDD重构了订单履约系统。整个过程就像破案事件风暴工作坊把业务方、开发、测试关在会议室8小时用便利贴梳理出关键事件订单已创建库存已预留支付已超时订单已自动取消识别聚合边界发现订单和库存应该属于不同限界上下文因为它们有独立生命周期变更频率不同库存秒级变化订单分钟级由不同团队维护定义上下文映射最终采用发布/订阅模式协调两个上下文graph LR 订单上下文 -- OrderCreated -- 消息队列 消息队列 -- 库存上下文3.2 代码落地规范在工程化实践中我们制定了严格的代码规范目录结构├── order-service │ ├── application # 应用服务 │ ├── domain # 领域模型 │ │ ├── model # 聚合根/实体 │ │ └── service # 领域服务 │ └── infrastructure # 技术实现聚合根示例public class Order extends BaseAggregateRoot { private OrderId id; private ListOrderItem items; private OrderStatus status; public static Order create(UserId userId, ListOrderItem items) { // 校验业务规则 if (items.isEmpty()) { throw new BusinessException(订单项不能为空); } Order order new Order(); order.id OrderId.next(); order.items new ArrayList(items); order.status OrderStatus.CREATED; order.addDomainEvent(new OrderCreatedEvent(order.id)); return order; } public void cancel() { if (!status.canCancel()) { throw new BusinessException(当前状态不可取消); } this.status OrderStatus.CANCELLED; addDomainEvent(new OrderCancelledEvent(id)); } }仓库实现public interface OrderRepository { Order findById(OrderId id); void save(Order order); } Repository public class OrderRepositoryImpl implements OrderRepository { Override public void save(Order order) { // 1. 保存聚合根状态 OrderPO po convertToPO(order); orderDao.save(po); // 2. 处理领域事件 for (DomainEvent event : order.getEvents()) { eventPublisher.publish(event); } } }4. 踩坑指南4.1 性能陷阱初期我们严格遵循一个事务只修改一个聚合根结果在订单支付流程中遇到了性能问题支付成功需要更新订单状态扣减库存增加用户积分这三个操作涉及不同聚合根如果完全分开会导致分布式事务问题解决方案引入Saga模式将大事务拆分为可补偿的子任务关键代码示例// Saga协调器 public class PaymentSaga { public void execute(PaymentContext context) { try { orderService.confirm(context.getOrderId()); inventoryService.deduct(context.getSku(), context.getQuantity()); userService.addPoints(context.getUserId(), context.getPoints()); } catch (Exception e) { // 触发补偿操作 orderService.cancel(context.getOrderId()); inventoryService.restore(context.getSku(), context.getQuantity()); } } }4.2 过度设计警告有个团队在初期把每个字段都建模成值对象导致简单查询变得复杂// 过度设计 public class Order { private OrderId id; private OrderNumber number; private UserId userId; private OrderStatus status; // ...其他20值对象 } // 适度设计 public class Order { private Long id; private String orderNo; private Long userId; private String status; // 基础类型直接使用 }经验法则会被多个实体引用的概念适合做成值对象如Money仅在当前实体使用的简单属性可直接用基础类型优先保持代码可读性必要时才引入DDD模式5. 测试策略DDD项目特别适合采用契约测试定义领域服务的接口契约编写消费者测试验证调用方期望编写提供者测试验证实现方承诺示例测试用例public class OrderServiceTest { Test public void should_emit_event_when_cancelling_order() { // 给定 Order order Order.create(...); orderRepository.save(order); // 当 orderService.cancel(order.getId()); // 则 DomainEvent event eventStore.findLatest(order.getId()); assertThat(event).isInstanceOf(OrderCancelledEvent.class); } }这种测试方式确保了业务意图明确表达技术实现不影响测试用例领域模型的行为可验证6. 演进式设计最后分享一个真实案例我们有个促销系统最初设计为Promotion (聚合根) ├── Rule └── Reward随着业务发展规则引擎变得复杂我们通过四步完成演进识别出Rule逐渐成为独立子域使用防腐层隔离新旧模型逐步将Rule相关逻辑迁移到新限界上下文最终架构变为促销上下文 --调用-- 规则引擎上下文 --触发-- 奖励发放上下文这个过程就像城市改造——不是推倒重来而是分区重建确保业务始终正常运行。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价