DDD领域驱动设计实战:从战略建模到战术落地的完整指南

DDD领域驱动设计实战:从战略建模到战术落地的完整指南
1. DDD领域驱动设计入门指南第一次接触DDD时我被各种新概念搞得头晕目眩——限界上下文、聚合根、值对象...直到参与电商订单系统重构项目才真正开窍。当时我们用了3周时间梳理出17个核心子域最终将原本5万行代码的大泥球拆解为8个微服务。最让我震撼的是当用DDD重新建模后原来需要修改5个模块的促销规则变更现在只需调整1个聚合根的逻辑。领域驱动设计Domain-Driven Design不是银弹但确实是应对复杂业务的利器。它像一台精密的翻译机把业务语言转化为代码结构。想象一下当产品经理说用户下单后要触发风控检查开发人员听到的却是Order聚合根调用RiskControl领域服务——这就是通用语言的魔力。2. 战略设计绘制业务地图2.1 子域划分实战去年做供应链系统改造时我们通过事件风暴工作坊识别出三个关键子域核心域采购寻源决定企业竞争力的核心算法支撑域供应商管理维护合格供应商名录通用域日志审计全系统通用的操作日志划分子域有个简单原则看业务变更频率。采购策略每周迭代属于核心域而供应商资质审核季度更新更适合作为支撑域。我曾见过团队把地址管理误划为核心域结果每次业务调整都要修改这个通用功能。2.2 限界上下文精确定义限界上下文是战略设计的核心成果。在物流系统中我们定义了这些边界// 运输上下文 class Shipment { private String trackingNumber; private Route route; public void updateLocation(GPSPoint point) {...} } // 仓储上下文 class Inventory { private String sku; private int quantity; public void reserve(int amount) {...} }注意这两个类都有location属性但在不同上下文中含义完全不同。运输关注实时位置仓储侧重存放位置。用防腐层隔离这两个上下文后仓储系统升级GPS格式时运输模块完全不受影响。2.3 事件风暴工作法最近一次库存系统改造中我们使用彩色便签进行事件风暴橙色便签记录事件库存已扣减蓝色便签标注命令提交出库单粉色便签标识业务规则库存不足时触发补货三天工作坊产出32个核心事件最终形成清晰的上下文映射图。关键收获是发现了虚拟库存这个隐藏领域概念这直接影响了后续微服务拆分方案。3. 战术建模代码落地细节3.1 聚合设计原则设计订单聚合时我们踩过典型的大聚合坑// 错误示范巨型聚合 class Order { private ListOrderItem items; private Payment payment; private Shipping shipping; private Invoice invoice; // 2000行业务逻辑... }后来遵循小聚合原则重构为class Order { private OrderId id; private ListOrderItem items; // 仅维护订单基本一致性 } class Payment { private PaymentId id; private OrderId orderId; // 通过ID引用 }经验值单个聚合对应数据库事务边界超过5个实体就该考虑拆分。3.2 领域服务vs应用服务在支付系统中我们这样划分职责// 领域服务包含核心业务逻辑 class PaymentService { public void process(Payment payment) { payment.validate(); // 业务规则校验 paymentGateway.charge(payment); // 基础设施调用 eventBus.publish(new PaymentCompleted(payment.id)); } } // 应用服务协调流程 class PaymentAppService { public void executePayment(Order order) { Payment payment paymentFactory.create(order); paymentService.process(payment); notificationService.sendReceipt(order.user); } }判断标准如果方法需要多个聚合协作通常应该放在领域服务。4. 经典案例解析电商订单系统4.1 订单状态建模初期我们使用简单枚举enum OrderStatus { CREATED, PAID, SHIPPED, COMPLETED }后来引入状态模式实现复杂流转class Order { private OrderState state; public void cancel() { state.cancel(this); } } interface OrderState { default void cancel(Order order) { throw new IllegalStateException(); } } class PaidState implements OrderState { Override public void cancel(Order order) { refundService.process(order); order.setState(new CancelledState()); } }这种设计完美支持了后来新增的部分退货状态。4.2 价格计算策略商品价格计算涉及多重规则interface PricingStrategy { Money calculate(Product product, User user); } class MemberPricing implements PricingStrategy { public Money calculate(Product product, User user) { return product.basePrice() .multiply(user.getDiscountRate()); } } // 使用策略模式组合 class CompositePricing { private ListPricingStrategy strategies; public Money calculate(Product p, User u) { Money result p.basePrice(); for (var strategy : strategies) { result strategy.calculate(p, u); } return result; } }当新增企业客户批量折扣时只需添加新策略类无需修改现有代码。5. 分层架构实现5.1 典型项目结构现代Java项目的包结构示例src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── eshop/ │ │ ├── order/ │ │ │ ├── application/ # 应用服务 │ │ │ ├── domain/ # 聚合根/实体 │ │ │ ├── infrastructure/ # 仓储实现 │ │ │ └── interfaces/ # REST控制器 │ │ └── payment/ └── test/ └── java/ └── com/ └── eshop/ ├── order/ └── payment/5.2 仓储模式实现JPA仓储的典型实现public class OrderRepositoryImpl implements OrderRepository { private final JpaOrderDao dao; Override public Order findById(OrderId id) { OrderEntity entity dao.findById(id); return entity.toDomain(); } Override public void save(Order order) { OrderEntity entity OrderEntity.fromDomain(order); dao.save(entity); } }关键点在infrastructure层实现接口领域对象与持久化对象分离转换逻辑放在Entity类中6. 常见问题解决方案6.1 事务边界问题当订单支付需要跨聚合修改账户余额时我们采用Transactional public void payOrder(OrderId orderId) { Order order orderRepo.findById(orderId); Account account accountRepo.findById(order.getAccountId()); order.pay(); account.debit(order.totalAmount()); // 使用领域事件保证最终一致性 eventPublisher.publish(new OrderPaid(orderId)); }配合定时任务处理失败事件替代分布式事务。6.2 复杂查询处理对于跨聚合的报表查询我们采用CQRS模式// 查询端单独模型 class OrderQueryService { public ListOrderDTO queryOrders(OrderQuery query) { return orderReadRepo.query(query); } } // 命令端保持简洁 class OrderCommandService { public void cancelOrder(OrderId id) { Order order orderRepo.findById(id); order.cancel(); } }读写分离后读模型可以直接使用SQL优化查询写模型保持领域纯度。7. 演进式设计实践7.1 从贫血模型到充血模型改造前的贫血模型Data public class Order { private Long id; private String status; // 只有getter/setter } Service public class OrderService { public void cancelOrder(Long id) { Order order orderDao.findById(id); order.setStatus(CANCELLED); orderDao.save(order); // 分散的业务逻辑... } }改造后的充血模型public class Order { private OrderId id; private OrderStatus status; public void cancel() { if (status.canCancel()) { status OrderStatus.CANCELLED; domainEvents.add(new OrderCancelled(id)); } } }改造关键将业务逻辑从Service移入实体用枚举实现状态机。7.2 微服务拆分路线我们的演进过程单体应用所有模块在同一个进程模块化单体按限界上下文分包微服务将高频变更的核心域独立部署每次拆分都基于领域模型变化频率指标。比如当促销规则每天变更而用户管理每月更新时就是拆分的信号。