资讯动态

DDD领域驱动设计实战指南

发布时间:2026/10/15 4:02:09 来源:尧图企业网站定制
DDD 领域驱动设计实战指南DDDDomain-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;privateListOrderItemitems;// 业务行为不是 getter/setter 堆砌publicvoidpay(Moneyamount){if(status!OrderStatus.PENDING_PAY){thrownewDomainException(订单状态不允许支付);}if(!totalAmount.equals(amount)){thrownewDomainException(支付金额不匹配);}this.statusOrderStatus.PAID;}publicvoidcancel(){if(statusOrderStatus.SHIPPED){thrownewDomainException(已发货订单不能取消);}this.statusOrderStatus.CANCELLED;}}核心业务规则写在实体里而不是 Service 里。3.2 值对象Value Object没有身份、由属性值定义、不可变的对象。// 金额值对象不可变publicfinalclassMoney{privatefinalBigDecimalamount;privatefinalCurrencycurrency;privateMoney(BigDecimalamount,Currencycurrency){this.amountamount;this.currencycurrency;}publicstaticMoneyof(BigDecimalamount,Currencycurrency){if(amountnull||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){OrderDOdoorderMapper.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;privatefinalListOrderItemitemsnewArrayList();privateOrder(CustomerIdcustomerId){this.idOrderId.generate();this.customerIdcustomerId;this.statusOrderStatus.CREATED;this.totalAmountMoney.zero();}publicstaticOrdercreate(CustomerIdcustomerId){returnnewOrder(customerId);}publicvoidaddItem(ProductIdproductId,StringproductName,Moneyprice,intcount){// 业务规则数量必须为正if(count0)thrownewDomainException(数量必须大于0);items.add(newOrderItem(productId,productName,price,count));this.totalAmountthis.totalAmount.add(price.multiply(count));}publicvoidconfirm(){if(items.isEmpty())thrownewDomainException(订单没有商品);this.statusOrderStatus.CONFIRMED;}}5.2 应用服务ServicepublicclassOrderApplicationService{AutowiredprivateOrderRepositoryorderRepository;AutowiredprivateStockDomainServicestockDomainService;TransactionalpublicOrderIdcreateOrder(CreateOrderCommandcmd){// 1. 创建订单领域逻辑OrderorderOrder.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 接口层RestControllerRequestMapping(/orders)publicclassOrderController{AutowiredprivateOrderApplicationServiceorderAppService;PostMappingpublicOrderVOcreate(RequestBodyCreateOrderRequestreq){OrderIdidorderAppService.createOrder(CreateOrderCommand.from(req));returnnewOrderVO(id.value());}}分层干净Controller 只做参数转换不碰业务。六、DDD 常见问题与坑问题原因解决实体还是一堆 getter/setter没把规则放进去把校验/状态流转写进实体Service 仍然巨长应用层和领域层混了流程留应用层规则下放领域聚合太大一个聚合装整个业务拆小聚合跨聚合用领域服务和数据库模型强耦合直接暴露 DO仓储转换领域与 DO 隔离简单项目过度设计滥用 DDD按业务复杂度取舍事务乱聚合边界不清一个聚合一个事务本章小结DDD 的落地要点战略上划清限界上下文战术上把业务规则放回领域模型。判断做没做对实体里有没有业务逻辑领域层有没有依赖 Mapper应用层是不是只剩编排三个都是是DDD 就落地了。记住——DDD 解决的是业务复杂度不是技术复杂度。下一篇讲 Linux Shell 脚本——运维自动化的基础功。全文完

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

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

免费获取报价 →
↑