什么是领域驱动设计(DDD)? 领域驱动设计(Domain-Driven Design,简称DDD)是一种软件开发方法论,由Eric Evans在2003年出版的同名书籍中首次提出。DDD强调以 业务领域 为核心驱动软件设计,通过建立清晰的领域模型来应对复杂的业务逻辑。
DDD的起源和背景 在传统的软件开发中,我们常常遇到这样的问题:
1 2 3 4 5 6 7 8 9 开发人员:这个功能应该怎么实现? 产品经理:用户点击按钮后,系统要... 开发人员:好的,我把逻辑写在Controller里 产品经理:不对,业务规则是... 开发人员:哦,那我加个if判断 几个月后... 产品经理:这个需求要改一下 开发人员:😭 这里改了,那里也要改,逻辑散落在各处...
问题的本质 :
业务逻辑散落在代码各处(Controller、Service、Util)
代码结构以技术分层为导向,而非业务逻辑
领域专家(业务人员)和开发人员之间存在沟通鸿沟
随着业务复杂度增加,代码越来越难以维护
Eric Evans提出DDD,核心思想是:让软件模型直接反映业务领域,让代码说业务语言 。
为什么需要DDD? 传统三层架构的问题 1 2 3 4 5 6 7 8 9 ├── Controller 层 (接收请求) │ └── OrderController.java ├── Service 层 (业务逻辑) │ └── OrderService.java ├── DAO 层 (数据访问) │ └── OrderDao.java └── Model 层 (数据模型) └── Order.java
问题1:贫血模型 - Model 只有getter/setter,没有业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Order { private Long id; private BigDecimal totalAmount; private String status; public Long getId () { return id; } public void setId (Long id) { this .id = id; } } @Service public class OrderService { public void createOrder (Order order) { if (order.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0 ) { throw new IllegalArgumentException ("订单金额必须大于0" ); } if (order.getUserLevel().equals("VIP" )) { order.setTotalAmount(order.getTotalAmount().multiply(new BigDecimal ("0.9" ))); } inventoryService.deductStock(order.getItems()); orderDao.save(order); notificationService.sendOrderConfirmation(order); } }
问题2:业务逻辑散落
订单验证逻辑在Service层
金额计算逻辑在Service层
状态转换逻辑在Service层
当需求变化时,要在多处修改代码
问题3:缺乏业务语义
代码无法直观反映业务规则
新人接手需要花大量时间理解业务
DDD的解决方案 1 2 3 4 5 6 7 8 9 10 11 12 ├── 订单上下文 (Order Context) │ ├── 领域层 (Domain Layer) │ │ ├── Order.java (订单聚合根 - 充血模型) │ │ ├── OrderItem.java (订单项实体) │ │ ├── Money.java (金额值对象) │ │ ├── OrderStatus.java (订单状态值对象) │ │ └── OrderRepository.java (订单仓储接口) │ ├── 应用层 (Application Layer) │ │ └── OrderApplicationService.java (应用服务) │ └── 基础设施层 (Infrastructure Layer) │ └── OrderRepositoryImpl.java (仓储实现)
充血的订单模型 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class Order { private OrderId id; private Money totalAmount; private OrderStatus status; private List<OrderItem> items; private UserId userId; public static Order create (UserId userId, List<OrderItem> items) { if (items == null || items.isEmpty()) { throw new OrderException ("订单至少包含一个商品" ); } Money totalAmount = Money.ZERO; for (OrderItem item : items) { totalAmount = totalAmount.add(item.getSubtotal()); } if (totalAmount.isLessThanOrEqualTo(Money.ZERO)) { throw new OrderException ("订单金额必须大于0" ); } Order order = new Order (); order.id = OrderId.generate(); order.userId = userId; order.items = items; order.totalAmount = totalAmount; order.status = OrderStatus.PENDING; DomainEventPublisher.publish(new OrderCreatedEvent (order.id)); return order; } public void pay (Money paymentAmount) { if (!this .status.equals(OrderStatus.PENDING)) { throw new OrderException ("订单状态不允许支付" ); } if (!paymentAmount.equals(this .totalAmount)) { throw new OrderException ("支付金额与订单金额不符" ); } this .status = OrderStatus.PAID; DomainEventPublisher.publish(new OrderPaidEvent (this .id, paymentAmount)); } public void cancel (String reason) { if (this .status.equals(OrderStatus.PAID)) { throw new OrderException ("已支付订单不能取消,请申请退款" ); } if (this .status.equals(OrderStatus.CANCELLED)) { throw new OrderException ("订单已取消" ); } this .status = OrderStatus.CANCELLED; DomainEventPublisher.publish(new OrderCancelledEvent (this .id, reason)); } public void applyDiscount (DiscountPolicy discountPolicy) { Money discountAmount = discountPolicy.calculate(this ); this .totalAmount = this .totalAmount.subtract(discountAmount); } }
对比优势 :
业务逻辑内聚 : 订单相关的所有业务规则都在Order类内部
清晰的业务语义 : order.pay() 比 orderService.updateOrderStatus(order, "PAID") 更能表达业务意图
不变性保护 : 业务规则在领域对象内部强制执行,外部无法绕过
易于测试 : 领域对象的业务逻辑可以独立测试,无需依赖数据库
DDD解决什么问题?
复杂业务建模
将复杂的业务领域分解为多个限界上下文
每个上下文专注于特定的业务能力
清晰的边界避免概念混淆
团队协作
建立统一语言(Ubiquitous Language)
业务人员和技术人员使用相同的术语
减少沟通成本和理解偏差
代码可维护性
业务逻辑集中在领域层
变更业务规则时,只需修改领域对象
技术实现与业务逻辑解耦
应对需求变化
领域模型直接反映业务规则
业务变化时,代码结构清晰易改
降低维护成本
DDD vs 传统三层架构
维度
传统三层架构
DDD架构
设计导向
以技术分层为导向
以业务领域为导向
领域模型
贫血模型(只有数据)
充血模型(数据+行为)
业务逻辑
集中在Service层
集中在Domain层
代码组织
按技术层次划分
按业务上下文划分
复杂度应对
容易混乱
清晰的边界和职责
适用场景
简单CRUD系统
复杂业务系统
学习成本
低
较高
何时使用DDD?
✅ 适合使用DDD :
业务逻辑复杂,规则多变
需要长期维护的系统
团队规模较大,需要清晰的边界
微服务架构,需要划分服务边界
❌ 不适合使用DDD :
简单的CRUD应用
原型项目或短期项目
技术驱动型项目(如工具类系统)
团队对DDD理解不足
DDD的核心概念 DDD分为**战略设计(Strategic Design)和 战术设计(Tactical Design)**两部分。
战略设计:如何划分业务领域 战略设计关注的是宏观层面的架构设计 ,即如何将复杂的业务领域拆分为多个子域,以及如何定义它们之间的关系。
1. 领域(Domain) **领域(Domain)**是指一个组织所从事的业务范围和活动范围。
示例 :
电商领域:商品、订单、支付、物流、营销等
银行领域:账户、转账、贷款、理财等
医疗领域:挂号、诊断、开药、收费等
一个领域通常会很大,包含多个业务模块,因此需要进一步拆分。
2. 子域(Subdomain) 将一个大的领域(Domain)拆分为多个 子域(Subdomain) ,每个子域专注于特定的业务能力。
子域分为三类:
核心域(Core Domain)
组织的核心竞争力所在
直接创造业务价值
需要投入最多资源
必须自己开发,不能外包
示例 :
1 2 3 4 电商系统的核心域: ├── 商品推荐算法 (差异化竞争力) ├── 会员体系 (用户粘性) └── 促销引擎 (营销能力)
支撑域(Supporting Domain)
支撑核心域运转
不直接创造价值,但必不可少
可以自己开发
示例 :
1 2 3 4 电商系统的支撑域: ├── 库存管理 ├── 订单管理 └── 用户管理
通用域(Generic Domain)
通用性的功能
不是业务特有的
建议使用第三方产品或开源方案
示例 :
1 2 3 4 5 电商系统的通用域: ├── 支付(使用支付宝/微信支付) ├── 物流(使用第三方物流) ├── 短信(使用阿里云短信) └── 认证(使用OAuth2)
子域划分示例:电商系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 电商领域(E-Commerce Domain) │ ├── 核心域(Core Domain) │ ├── 商品推荐子域 (Recommendation Subdomain) │ ├── 会员权益子域 (Membership Subdomain) │ └── 营销活动子域 (Marketing Subdomain) │ ├── 支撑域(Supporting Domain) │ ├── 商品管理子域 (Product Subdomain) │ ├── 订单管理子域 (Order Subdomain) │ ├── 库存管理子域 (Inventory Subdomain) │ └── 用户管理子域 (User Subdomain) │ └── 通用域(Generic Domain) ├── 支付子域 (Payment Subdomain) ├── 物流子域 (Logistics Subdomain) ├── 短信通知子域 (Notification Subdomain) └── 认证授权子域 (Authentication Subdomain)
3. 限界上下文(Bounded Context) 限界上下文(Bounded Context)是DDD中最重要的战略模式,它定义了一个 明确的边界 ,在这个边界内,所有的术语、概念、规则都有明确且一致的含义。
为什么需要限界上下文?
同一个概念在不同的业务场景下,含义可能完全不同:
示例:”商品”的不同含义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 在商品上下文中: ├── 商品(Product) │ ├── SKU (库存单元) │ ├── 价格 │ ├── 库存 │ └── 商品属性 在订单上下文中: ├── 商品(Product) │ ├── 商品ID │ ├── 购买时的价格 (下单时的快照) │ ├── 购买数量 │ └── 小计金额 在推荐上下文中: ├── 商品(Product) │ ├── 商品ID │ ├── 点击率 │ ├── 转化率 │ ├── 用户评分 │ └── 推荐权重
可以看到,”商品”在三个上下文中的属性和关注点完全不同。如果没有明确的边界,就会导致:
概念混淆:不清楚商品的价格是当前价还是下单时的价格
代码耦合:修改商品管理影响到订单系统
难以维护:一个”商品”类承载了过多职责
限界上下文的关键特征 :
统一语言(Ubiquitous Language) : 上下文内使用一致的术语
明确边界 : 清楚定义哪些属于这个上下文,哪些不属于
独立演化 : 每个上下文可以独立开发和部署
隔离性 : 一个上下文的变化不影响其他上下文
限界上下文划分示例:电商系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 电商系统 │ ├── 商品上下文(Product Context) │ ├── 商品(Product) │ ├── 分类(Category) │ ├── 品牌(Brand) │ └── SKU │ ├── 订单上下文(Order Context) │ ├── 订单(Order) │ ├── 订单项(OrderItem) <-- 商品快照 │ ├── 收货地址(Address) │ └── 发票(Invoice) │ ├── 库存上下文(Inventory Context) │ ├── 库存(Stock) │ ├── 仓库(Warehouse) │ └── 库存流水(StockLog) │ ├── 支付上下文(Payment Context) │ ├── 支付单(Payment) │ ├── 支付方式(PaymentMethod) │ └── 退款(Refund) │ └── 会员上下文(Member Context) ├── 会员(Member) ├── 会员等级(MemberLevel) └── 积分(Points)
代码示例:不同上下文中的”商品”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package com.ecommerce.product.domain;public class Product { private ProductId id; private String name; private String description; private Category category; private Brand brand; private Price currentPrice; private List<SKU> skus; private ProductStatus status; public void updatePrice (Price newPrice) { } public void publish () { } public void offsale () { } } package com.ecommerce.order.domain;public class OrderItem { private OrderItemId id; private ProductId productId; private String productName; private Money snapshotPrice; private int quantity; private Money subtotal; public Money calculateSubtotal () { return snapshotPrice.multiply(quantity); } } package com.ecommerce.recommendation.domain;public class RecommendationProduct { private ProductId productId; private double clickRate; private double conversionRate; private double userRating; private int salesVolume; private double recommendationScore; public double calculateScore (UserProfile userProfile) { } }
4. 上下文映射(Context Map) **上下文映射(Context Map)**定义了不同限界上下文之间的关系和集成方式。
常见的上下文关系模式:
共享内核(Shared Kernel) 两个上下文共享一部分领域模型
1 2 3 订单上下文 ←─[共享]─→ 支付上下文 ↓ 共享内核:订单ID、金额
客户-供应商(Customer-Supplier) 供应商提供服务,客户依赖供应商
1 2 3 订单上下文(客户) → 商品上下文(供应商) ↓ 商品信息API
防腐层(Anti-Corruption Layer,ACL) 使用适配器隔离外部系统,避免污染自己的领域模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public interface PaymentGateway { PaymentResult pay (PaymentRequest request) ; } @Component public class AlipayAdapter implements PaymentGateway { @Autowired private AlipayClient alipayClient; @Override public PaymentResult pay (PaymentRequest request) { AlipayTradePayRequest alipayRequest = new AlipayTradePayRequest (); alipayRequest.setOutTradeNo(request.getOrderId()); alipayRequest.setTotalAmount(request.getAmount().toString()); AlipayTradePayResponse response = alipayClient.execute(alipayRequest); return PaymentResult.builder() .success(response.isSuccess()) .transactionId(response.getTradeNo()) .build(); } }
开放主机服务(Open Host Service) 为外部系统提供标准化的API
1 2 3 4 5 6 7 8 9 @RestController @RequestMapping("/api/orders") public class OrderController { @GetMapping("/{orderId}") public OrderDTO getOrder (@PathVariable String orderId) { } }
战术设计:如何实现业务领域 战术设计关注的是微观层面的代码设计 ,即如何用代码实现领域模型。
1. 实体(Entity) **实体(Entity)**是具有唯一标识的对象,即使属性完全相同,只要标识不同,就是不同的实体。
核心特征 :
有唯一标识(ID)
可变性(属性可以改变,但标识不变)
连续性(生命周期内保持身份一致)
判断标准 :
1 2 3 4 5 6 两个对象是否相等,取决于标识而非属性 用户A:ID=1, name="张三", age=25 用户B:ID=1, name="张三", age=26 <-- 年龄变了,但还是同一个用户 用户C:ID=2, name="张三", age=25 <-- 属性完全相同,但是不同的用户
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class Order { private OrderId id; private UserId userId; private List<OrderItem> items; private Money totalAmount; private OrderStatus status; private LocalDateTime createdAt; private Order () {} public static Order create (UserId userId, List<OrderItem> items) { Order order = new Order (); order.id = OrderId.generate(); order.userId = userId; order.items = items; order.totalAmount = calculateTotal(items); order.status = OrderStatus.PENDING; order.createdAt = LocalDateTime.now(); return order; } @Override public boolean equals (Object o) { if (this == o) return true ; if (!(o instanceof Order)) return false ; Order order = (Order) o; return Objects.equals(id, order.id); } @Override public int hashCode () { return Objects.hash(id); } } public class OrderId { private final String value; private OrderId (String value) { this .value = Objects.requireNonNull(value, "订单ID不能为空" ); } public static OrderId generate () { return new OrderId (UUID.randomUUID().toString()); } public static OrderId of (String value) { return new OrderId (value); } public String getValue () { return value; } @Override public boolean equals (Object o) { if (this == o) return true ; if (!(o instanceof OrderId)) return false ; OrderId orderId = (OrderId) o; return Objects.equals(value, orderId.value); } @Override public int hashCode () { return Objects.hash(value); } }
2. 值对象(Value Object) 值对象(Value Object)是没有唯一标识的对象,完全由属性值决定其身份,强调 不可变性 。
核心特征 :
无唯一标识
不可变(Immutable)
可替换
值相等(属性相同则对象相等)
判断标准 :
1 2 3 4 5 两个对象是否相等,取决于所有属性值是否相同 金额A:100元 金额B:100元 <-- 属性相同,就是相同的金额,可以互换 金额C:200元 <-- 属性不同,是不同的金额
为什么要用值对象?
不使用值对象:
1 2 3 4 5 6 7 8 public class Order { private BigDecimal totalAmount; public void applyDiscount (BigDecimal discountPercent) { this .totalAmount = totalAmount.multiply(BigDecimal.ONE.subtract(discountPercent)); } }
使用值对象:
1 2 3 4 5 6 7 8 public class Order { private Money totalAmount; public void applyDiscount (Percentage discount) { this .totalAmount = totalAmount.discount(discount); } }
值对象的优势 :
类型安全 : 不会混淆金额和数量
业务语义 : Money 比 BigDecimal 更能表达业务含义
不变性 : 线程安全,可以安全共享
业务逻辑内聚 : 金额相关的计算逻辑都在 Money 类内部
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 public class Money { private final BigDecimal amount; private final String currency; public static final Money ZERO = new Money (BigDecimal.ZERO, "CNY" ); private Money (BigDecimal amount, String currency) { if (amount == null ) { throw new IllegalArgumentException ("金额不能为空" ); } if (amount.scale() > 2 ) { throw new IllegalArgumentException ("金额最多保留两位小数" ); } this .amount = amount; this .currency = currency; } public static Money of (BigDecimal amount, String currency) { return new Money (amount, currency); } public static Money yuan (double amount) { return new Money (BigDecimal.valueOf(amount), "CNY" ); } public Money add (Money other) { assertSameCurrency(other); return new Money (this .amount.add(other.amount), this .currency); } public Money subtract (Money other) { assertSameCurrency(other); return new Money (this .amount.subtract(other.amount), this .currency); } public Money multiply (int multiplier) { return new Money (this .amount.multiply(BigDecimal.valueOf(multiplier)), this .currency); } public Money discount (Percentage percentage) { BigDecimal discountAmount = this .amount.multiply(percentage.getValue()); return new Money (this .amount.subtract(discountAmount), this .currency); } public boolean isGreaterThan (Money other) { assertSameCurrency(other); return this .amount.compareTo(other.amount) > 0 ; } public boolean isLessThanOrEqualTo (Money other) { assertSameCurrency(other); return this .amount.compareTo(other.amount) <= 0 ; } private void assertSameCurrency (Money other) { if (!this .currency.equals(other.currency)) { throw new IllegalArgumentException ("货币类型不同,无法计算" ); } } @Override public boolean equals (Object o) { if (this == o) return true ; if (!(o instanceof Money)) return false ; Money money = (Money) o; return amount.compareTo(money.amount) == 0 && Objects.equals(currency, money.currency); } @Override public int hashCode () { return Objects.hash(amount, currency); } @Override public String toString () { return currency + " " + amount; } } public class Percentage { private final BigDecimal value; private Percentage (BigDecimal value) { if (value.compareTo(BigDecimal.ZERO) < 0 || value.compareTo(BigDecimal.ONE) > 1 ) { throw new IllegalArgumentException ("百分比必须在0-1之间" ); } this .value = value; } public static Percentage of (double value) { return new Percentage (BigDecimal.valueOf(value)); } public BigDecimal getValue () { return value; } @Override public boolean equals (Object o) { if (this == o) return true ; if (!(o instanceof Percentage)) return false ; Percentage that = (Percentage) o; return value.compareTo(that.value) == 0 ; } @Override public int hashCode () { return Objects.hash(value); } } public class Address { private final String province; private final String city; private final String district; private final String street; private final String detail; private final String zipCode; private Address (String province, String city, String district, String street, String detail, String zipCode) { this .province = Objects.requireNonNull(province, "省份不能为空" ); this .city = Objects.requireNonNull(city, "城市不能为空" ); this .district = Objects.requireNonNull(district, "区县不能为空" ); this .street = Objects.requireNonNull(street, "街道不能为空" ); this .detail = Objects.requireNonNull(detail, "详细地址不能为空" ); this .zipCode = zipCode; } public static Address of (String province, String city, String district, String street, String detail, String zipCode) { return new Address (province, city, district, street, detail, zipCode); } public String getFullAddress () { return province + city + district + street + detail; } public boolean isSameCity (Address other) { return this .province.equals(other.province) && this .city.equals(other.city); } public String getProvince () { return province; } public String getCity () { return city; } @Override public boolean equals (Object o) { if (this == o) return true ; if (!(o instanceof Address)) return false ; Address address = (Address) o; return Objects.equals(province, address.province) && Objects.equals(city, address.city) && Objects.equals(district, address.district) && Objects.equals(street, address.street) && Objects.equals(detail, address.detail) && Objects.equals(zipCode, address.zipCode); } @Override public int hashCode () { return Objects.hash(province, city, district, street, detail, zipCode); } }
实体 vs 值对象对比
维度
实体(Entity)
值对象(Value Object)
唯一标识
有ID
无ID
相等性
基于ID
基于所有属性
可变性
可变
不可变
生命周期
有独立生命周期
依附于实体
替换性
不可随意替换
可以替换
示例
Order, User, Product
Money, Address, Email
3. 聚合(Aggregate)与聚合根(Aggregate Root) **聚合(Aggregate)是一组相关对象的集合,作为一个整体来管理数据的一致性。 聚合根(Aggregate Root)**是聚合的入口,外部只能通过聚合根访问聚合内的对象。
为什么需要聚合?
没有聚合的问题:
1 2 3 4 OrderItem item = order.getItems().get(0 );item.setQuantity(1000 );
使用聚合:
1 2 order.updateItemQuantity(itemId, 1000 );
聚合的设计原则 :
聚合根是唯一入口 : 外部只能持有聚合根的引用
边界内保证一致性 : 聚合内的所有对象一起保存、一起删除
聚合间只能引用ID : 不能直接持有其他聚合根的引用
聚合不宜过大 : 通常只包含1-3个实体
代码示例:订单聚合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 public class Order { private OrderId id; private UserId userId; private List<OrderItem> items; private Money totalAmount; private Address shippingAddress; private OrderStatus status; private LocalDateTime createdAt; private Order () { this .items = new ArrayList <>(); } public static Order create (UserId userId, List<OrderItem> items, Address shippingAddress) { if (items == null || items.isEmpty()) { throw new OrderException ("订单至少包含一个商品" ); } Order order = new Order (); order.id = OrderId.generate(); order.userId = userId; order.items = new ArrayList <>(items); order.shippingAddress = shippingAddress; order.totalAmount = calculateTotalAmount(items); order.status = OrderStatus.PENDING; order.createdAt = LocalDateTime.now(); DomainEventPublisher.publish(new OrderCreatedEvent (order.id)); return order; } public void addItem (OrderItem newItem) { if (this .status != OrderStatus.PENDING) { throw new OrderException ("只有待支付订单才能添加商品" ); } Optional<OrderItem> existingItem = items.stream() .filter(item -> item.getProductId().equals(newItem.getProductId())) .findFirst(); if (existingItem.isPresent()) { existingItem.get().increaseQuantity(newItem.getQuantity()); } else { items.add(newItem); } this .totalAmount = calculateTotalAmount(items); } public void updateItemQuantity (OrderItemId itemId, int newQuantity) { if (this .status != OrderStatus.PENDING) { throw new OrderException ("只有待支付订单才能修改商品数量" ); } OrderItem item = findItem(itemId); item.updateQuantity(newQuantity); this .totalAmount = calculateTotalAmount(items); } public void removeItem (OrderItemId itemId) { if (this .status != OrderStatus.PENDING) { throw new OrderException ("只有待支付订单才能移除商品" ); } boolean removed = items.removeIf(item -> item.getId().equals(itemId)); if (!removed) { throw new OrderException ("订单项不存在" ); } if (items.isEmpty()) { throw new OrderException ("订单至少包含一个商品" ); } this .totalAmount = calculateTotalAmount(items); } public void pay (Money paymentAmount) { if (this .status != OrderStatus.PENDING) { throw new OrderException ("订单状态不允许支付" ); } if (!paymentAmount.equals(this .totalAmount)) { throw new OrderException ("支付金额与订单金额不符" ); } this .status = OrderStatus.PAID; DomainEventPublisher.publish(new OrderPaidEvent (this .id, paymentAmount)); } public void cancel (String reason) { if (this .status == OrderStatus.PAID) { throw new OrderException ("已支付订单不能取消,请申请退款" ); } if (this .status == OrderStatus.CANCELLED) { throw new OrderException ("订单已取消" ); } this .status = OrderStatus.CANCELLED; DomainEventPublisher.publish(new OrderCancelledEvent (this .id, reason)); } private OrderItem findItem (OrderItemId itemId) { return items.stream() .filter(item -> item.getId().equals(itemId)) .findFirst() .orElseThrow(() -> new OrderException ("订单项不存在" )); } private static Money calculateTotalAmount (List<OrderItem> items) { return items.stream() .map(OrderItem::getSubtotal) .reduce(Money.ZERO, Money::add); } public OrderId getId () { return id; } public UserId getUserId () { return userId; } public Money getTotalAmount () { return totalAmount; } public OrderStatus getStatus () { return status; } public List<OrderItem> getItems () { return Collections.unmodifiableList(items); } } public class OrderItem { private OrderItemId id; private ProductId productId; private String productName; private Money unitPrice; private int quantity; private Money subtotal; OrderItem(ProductId productId, String productName, Money unitPrice, int quantity) { if (quantity <= 0 ) { throw new IllegalArgumentException ("数量必须大于0" ); } this .id = OrderItemId.generate(); this .productId = productId; this .productName = productName; this .unitPrice = unitPrice; this .quantity = quantity; this .subtotal = unitPrice.multiply(quantity); } void updateQuantity (int newQuantity) { if (newQuantity <= 0 ) { throw new IllegalArgumentException ("数量必须大于0" ); } this .quantity = newQuantity; this .subtotal = unitPrice.multiply(quantity); } void increaseQuantity (int increment) { this .quantity += increment; this .subtotal = unitPrice.multiply(quantity); } public OrderItemId getId () { return id; } public ProductId getProductId () { return productId; } public String getProductName () { return productName; } public Money getUnitPrice () { return unitPrice; } public int getQuantity () { return quantity; } public Money getSubtotal () { return subtotal; } }
聚合间的引用 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Order { private User user; } public class Order { private UserId userId; } @Service public class OrderApplicationService { @Autowired private OrderRepository orderRepository; @Autowired private UserRepository userRepository; @Transactional public OrderDTO getOrderDetail (OrderId orderId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException ()); User user = userRepository.findById(order.getUserId()) .orElseThrow(() -> new UserNotFoundException ()); return OrderDTO.from(order, user); } }
聚合设计的关键问题 :
聚合的大小
✅ 小聚合(1-3个实体):性能好,易于维护
❌ 大聚合(10+个实体):性能差,并发冲突多
聚合的边界
遵循业务规则:一起变化的放在一起
考虑事务边界:一个事务只修改一个聚合
考虑并发性能:聚合越小,锁冲突越少
聚合间的一致性
聚合内:强一致性(同一事务)
聚合间:最终一致性(领域事件)
DDD分层架构 DDD通常采用四层架构 ,从上到下依次是:
1 2 3 4 5 6 7 8 9 ┌─────────────────────────────────────┐ │ 用户界面层 (User Interface) │ ← 展示和交互 ├─────────────────────────────────────┤ │ 应用层 (Application Layer) │ ← 编排和协调 ├─────────────────────────────────────┤ │ 领域层 (Domain Layer) │ ← 核心业务逻辑 ├─────────────────────────────────────┤ │ 基础设施层 (Infrastructure Layer) │ ← 技术实现 └─────────────────────────────────────┘
1. 用户界面层(User Interface Layer) 职责 :
包含 :
Controller
DTO(Data Transfer Object)
视图模板
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 @RestController @RequestMapping("/api/orders") public class OrderController { @Autowired private OrderApplicationService orderApplicationService; @PostMapping public Response<OrderDTO> createOrder (@RequestBody CreateOrderRequest request) { OrderDTO order = orderApplicationService.createOrder(request); return Response.success(order); } @PostMapping("/{orderId}/pay") public Response<Void> payOrder ( @PathVariable String orderId, @RequestBody PaymentRequest request ) { orderApplicationService.payOrder(OrderId.of(orderId), request); return Response.success(); } @GetMapping("/{orderId}") public Response<OrderDTO> getOrder (@PathVariable String orderId) { OrderDTO order = orderApplicationService.getOrder(OrderId.of(orderId)); return Response.success(order); } } public class OrderDTO { private String orderId; private String userId; private List<OrderItemDTO> items; private BigDecimal totalAmount; private String status; private String shippingAddress; private LocalDateTime createdAt; public static OrderDTO from (Order order) { OrderDTO dto = new OrderDTO (); dto.orderId = order.getId().getValue(); dto.userId = order.getUserId().getValue(); dto.items = order.getItems().stream() .map(OrderItemDTO::from) .collect(Collectors.toList()); dto.totalAmount = order.getTotalAmount().getAmount(); dto.status = order.getStatus().name(); dto.shippingAddress = order.getShippingAddress().getFullAddress(); dto.createdAt = order.getCreatedAt(); return dto; } }
2. 应用层(Application Layer) 职责 :
编排领域对象完成业务流程
协调多个聚合
管理事务
发布和订阅领域事件
权限校验
特点 :
薄薄的一层 : 不包含业务逻辑,只负责协调
无状态 : 不保存业务状态
事务边界 : 一个应用服务方法 = 一个事务
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 @Service @Transactional public class OrderApplicationService { @Autowired private OrderRepository orderRepository; @Autowired private ProductRepository productRepository; @Autowired private UserRepository userRepository; @Autowired private InventoryService inventoryService; @Autowired private DomainEventPublisher eventPublisher; public OrderDTO createOrder (CreateOrderRequest request) { if (request.getItems() == null || request.getItems().isEmpty()) { throw new IllegalArgumentException ("订单至少包含一个商品" ); } UserId userId = UserId.of(request.getUserId()); User user = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException ()); List<OrderItem> orderItems = new ArrayList <>(); for (CreateOrderItemRequest itemReq : request.getItems()) { ProductId productId = ProductId.of(itemReq.getProductId()); Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException ()); if (!inventoryService.checkStock(productId, itemReq.getQuantity())) { throw new InsufficientStockException ("商品库存不足" ); } OrderItem orderItem = new OrderItem ( productId, product.getName(), product.getCurrentPrice(), itemReq.getQuantity() ); orderItems.add(orderItem); } Address shippingAddress = Address.of( request.getProvince(), request.getCity(), request.getDistrict(), request.getStreet(), request.getDetail(), request.getZipCode() ); Order order = Order.create(userId, orderItems, shippingAddress); if (user.isVip()) { DiscountPolicy vipDiscount = new VipDiscountPolicy (); order.applyDiscount(vipDiscount); } orderRepository.save(order); for (OrderItem item : orderItems) { inventoryService.deductStock(item.getProductId(), item.getQuantity()); } eventPublisher.publish(new OrderCreatedEvent (order.getId())); return OrderDTO.from(order); } public void payOrder (OrderId orderId, PaymentRequest request) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException ()); Money paymentAmount = Money.yuan(request.getAmount()); order.pay(paymentAmount); orderRepository.save(order); eventPublisher.publish(new OrderPaidEvent (orderId, paymentAmount)); } public void cancelOrder (OrderId orderId, String reason) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException ()); order.cancel(reason); orderRepository.save(order); for (OrderItem item : order.getItems()) { inventoryService.restoreStock(item.getProductId(), item.getQuantity()); } eventPublisher.publish(new OrderCancelledEvent (orderId, reason)); } @Transactional(readOnly = true) public OrderDTO getOrder (OrderId orderId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException ()); return OrderDTO.from(order); } }
应用服务 vs 领域服务 :
维度
应用服务
领域服务
职责
编排协调
业务逻辑
状态
无状态
可以有状态
事务
管理事务
不关心事务
依赖
依赖多个聚合
依赖领域对象
示例
OrderApplicationService
PricingService
3. 领域层(Domain Layer) 职责 :
实现核心业务逻辑
定义领域模型
包含业务规则和约束
包含 :
实体(Entity)
值对象(Value Object)
聚合(Aggregate)
领域服务(Domain Service)
领域事件(Domain Event)
仓储接口(Repository Interface)
工厂(Factory)
示例 :前面已经展示了Order、OrderItem、Money等领域对象
4. 基础设施层(Infrastructure Layer) 职责 :
为其他层提供技术支撑
实现仓储接口
实现消息发布
实现配置管理
包含 :
仓储实现(Repository Implementation)
消息发送
缓存
日志
配置
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 @Repository public class OrderRepositoryImpl implements OrderRepository { @Autowired private JpaOrderRepository jpaRepository; @Autowired private OrderMapper orderMapper; @Override public Optional<Order> findById (OrderId orderId) { return jpaRepository.findById(orderId.getValue()) .map(orderMapper::toDomain); } @Override public void save (Order order) { OrderPO po = orderMapper.toPO(order); jpaRepository.save(po); } @Override public void delete (Order order) { jpaRepository.deleteById(order.getId().getValue()); } } public interface JpaOrderRepository extends JpaRepository <OrderPO, String> {} @Entity @Table(name = "orders") public class OrderPO { @Id private String id; @Column(name = "user_id") private String userId; @Column(name = "total_amount") private BigDecimal totalAmount; @Column(name = "status") private String status; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "order_id") private List<OrderItemPO> items; private String province; private String city; private String district; private String street; private String detail; private String zipCode; @Column(name = "created_at") private LocalDateTime createdAt; } @Entity @Table(name = "order_items") public class OrderItemPO { @Id private String id; @Column(name = "order_id") private String orderId; @Column(name = "product_id") private String productId; @Column(name = "product_name") private String productName; @Column(name = "unit_price") private BigDecimal unitPrice; @Column(name = "quantity") private Integer quantity; @Column(name = "subtotal") private BigDecimal subtotal; } @Component public class OrderMapper { public Order toDomain (OrderPO po) { List<OrderItem> items = po.getItems().stream() .map(this ::toDomainItem) .collect(Collectors.toList()); Address address = Address.of( po.getProvince(), po.getCity(), po.getDistrict(), po.getStreet(), po.getDetail(), po.getZipCode() ); return Order.reconstruct( OrderId.of(po.getId()), UserId.of(po.getUserId()), items, Money.of(po.getTotalAmount(), "CNY" ), address, OrderStatus.valueOf(po.getStatus()), po.getCreatedAt() ); } private OrderItem toDomainItem (OrderItemPO po) { return OrderItem.reconstruct( OrderItemId.of(po.getId()), ProductId.of(po.getProductId()), po.getProductName(), Money.of(po.getUnitPrice(), "CNY" ), po.getQuantity() ); } public OrderPO toPO (Order order) { OrderPO po = new OrderPO (); po.setId(order.getId().getValue()); po.setUserId(order.getUserId().getValue()); po.setTotalAmount(order.getTotalAmount().getAmount()); po.setStatus(order.getStatus().name()); po.setProvince(order.getShippingAddress().getProvince()); po.setCity(order.getShippingAddress().getCity()); po.setDistrict(order.getShippingAddress().getDistrict()); po.setStreet(order.getShippingAddress().getStreet()); po.setDetail(order.getShippingAddress().getDetail()); po.setZipCode(order.getShippingAddress().getZipCode()); po.setCreatedAt(order.getCreatedAt()); List<OrderItemPO> itemPOs = order.getItems().stream() .map(this ::toPOItem) .collect(Collectors.toList()); po.setItems(itemPOs); return po; } private OrderItemPO toPOItem (OrderItem item) { OrderItemPO po = new OrderItemPO (); po.setId(item.getId().getValue()); po.setProductId(item.getProductId().getValue()); po.setProductName(item.getProductName()); po.setUnitPrice(item.getUnitPrice().getAmount()); po.setQuantity(item.getQuantity()); po.setSubtotal(item.getSubtotal().getAmount()); return po; } }
分层架构的依赖关系 :
1 2 3 用户界面层 ──→ 应用层 ──→ 领域层 ↓ ↓ ↑ └───────→ 基础设施层 ──┘
关键原则 :
依赖倒置 : 基础设施层实现领域层定义的接口
领域层独立 : 领域层不依赖任何其他层
应用层协调 : 应用层编排领域对象和基础设施
4. 领域服务(Domain Service) **领域服务(Domain Service)**用于封装不属于任何实体或值对象的业务逻辑,通常涉及多个聚合或值对象的协作。
何时使用领域服务?
当遇到以下情况时,考虑使用领域服务:
业务逻辑涉及多个聚合
业务逻辑不自然属于任何一个实体
无状态的业务操作
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public interface PricingService { Money calculateOrderPrice (Order order) ; } @Service public class PricingServiceImpl implements PricingService { @Override public Money calculateOrderPrice (Order order) { Money basePrice = order.getTotalAmount(); Money discountedPrice = basePrice; if (basePrice.isGreaterThan(Money.yuan(1000 ))) { discountedPrice = basePrice.subtract(Money.yuan(100 )); } else if (basePrice.isGreaterThan(Money.yuan(500 ))) { discountedPrice = basePrice.subtract(Money.yuan(50 )); } return discountedPrice; } } public interface TransferService { void transfer (AccountId fromAccountId, AccountId toAccountId, Money amount) ; } @Service public class TransferServiceImpl implements TransferService { @Autowired private AccountRepository accountRepository; @Override @Transactional public void transfer (AccountId fromAccountId, AccountId toAccountId, Money amount) { Account fromAccount = accountRepository.findById(fromAccountId) .orElseThrow(() -> new AccountNotFoundException ()); Account toAccount = accountRepository.findById(toAccountId) .orElseThrow(() -> new AccountNotFoundException ()); fromAccount.withdraw(amount); toAccount.deposit(amount); accountRepository.save(fromAccount); accountRepository.save(toAccount); DomainEventPublisher.publish(new TransferCompletedEvent (fromAccountId, toAccountId, amount)); } }
领域服务 vs 应用服务 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Service public class PricingService { public Money calculateDiscount (Order order, User user) { if (user.isVip()) { return order.getTotalAmount().multiply(0.1 ); } else { return order.getTotalAmount().multiply(0.05 ); } } } @Service @Transactional public class OrderApplicationService { @Autowired private PricingService pricingService; public OrderDTO createOrder (CreateOrderRequest request) { Order order = Order.create(...); User user = userRepository.findById(...); Money discount = pricingService.calculateDiscount(order, user); order.applyDiscount(discount); orderRepository.save(order); return OrderDTO.from(order); } }
5. 领域事件(Domain Event) **领域事件(Domain Event)**表示领域中发生的重要业务事件,用于实现聚合间的最终一致性和解耦。
为什么需要领域事件?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Service public class OrderService { @Autowired private InventoryService inventoryService; @Autowired private NotificationService notificationService; @Autowired private PointsService pointsService; public void payOrder (OrderId orderId) { Order order = orderRepository.findById(orderId).orElseThrow(); order.pay(); inventoryService.reserveStock(order); notificationService.sendPaymentNotification(order); pointsService.addPoints(order); orderRepository.save(order); } } @Service public class OrderService { public void payOrder (OrderId orderId) { Order order = orderRepository.findById(orderId).orElseThrow(); order.pay(); orderRepository.save(order); } }
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 public abstract class DomainEvent { private final String eventId; private final LocalDateTime occurredOn; protected DomainEvent () { this .eventId = UUID.randomUUID().toString(); this .occurredOn = LocalDateTime.now(); } public String getEventId () { return eventId; } public LocalDateTime getOccurredOn () { return occurredOn; } } public class OrderCreatedEvent extends DomainEvent { private final OrderId orderId; public OrderCreatedEvent (OrderId orderId) { super (); this .orderId = orderId; } public OrderId getOrderId () { return orderId; } } public class OrderPaidEvent extends DomainEvent { private final OrderId orderId; private final Money paymentAmount; public OrderPaidEvent (OrderId orderId, Money paymentAmount) { super (); this .orderId = orderId; this .paymentAmount = paymentAmount; } public OrderId getOrderId () { return orderId; } public Money getPaymentAmount () { return paymentAmount; } } @Component public class DomainEventPublisher { private static ApplicationEventPublisher publisher; @Autowired public void setPublisher (ApplicationEventPublisher publisher) { DomainEventPublisher.publisher = publisher; } public static void publish (DomainEvent event) { if (publisher != null ) { publisher.publishEvent(event); } } } public class Order { public void pay (Money paymentAmount) { this .status = OrderStatus.PAID; DomainEventPublisher.publish(new OrderPaidEvent (this .id, paymentAmount)); } } @Component public class OrderEventListener { @Autowired private InventoryService inventoryService; @Autowired private NotificationService notificationService; @Autowired private PointsService pointsService; @EventListener @Transactional(propagation = Propagation.REQUIRES_NEW) public void handleOrderPaid (OrderPaidEvent event) { OrderId orderId = event.getOrderId(); try { inventoryService.deductStock(orderId); notificationService.sendPaymentSuccessNotification(orderId); pointsService.addPoints(orderId, event.getPaymentAmount()); } catch (Exception e) { log.error("处理订单支付事件失败: orderId={}" , orderId, e); } } @EventListener public void handleOrderCancelled (OrderCancelledEvent event) { inventoryService.restoreStock(event.getOrderId()); notificationService.sendCancellationNotification(event.getOrderId()); } }
领域事件的优势 :
解耦 : 聚合之间通过事件通信,而非直接调用
可扩展 : 新增事件监听器不影响原有代码
最终一致性 : 支持异步处理,提高性能
审计追踪 : 事件记录了业务发生的历史
6. 仓储(Repository) **仓储(Repository)**提供了聚合的持久化和查询接口,封装了数据访问的细节。
仓储的特点 :
只为聚合根提供仓储
仓储接口定义在领域层
仓储实现在基础设施层
仓储使用领域对象,而非数据模型
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public interface OrderRepository { Optional<Order> findById (OrderId orderId) ; void save (Order order) ; void delete (Order order) ; List<Order> findByUserId (UserId userId) ; List<Order> findByStatus (OrderStatus status) ; Optional<Order> findByIdWithItems (OrderId orderId) ; } @Repository public class OrderRepositoryImpl implements OrderRepository { @Autowired private JpaOrderRepository jpaRepository; @Autowired private OrderMapper orderMapper; @Override public Optional<Order> findById (OrderId orderId) { return jpaRepository.findById(orderId.getValue()) .map(orderMapper::toDomain); } @Override public void save (Order order) { OrderPO po = orderMapper.toPO(order); jpaRepository.save(po); } @Override public void delete (Order order) { jpaRepository.deleteById(order.getId().getValue()); } @Override public List<Order> findByUserId (UserId userId) { List<OrderPO> pos = jpaRepository.findByUserId(userId.getValue()); return pos.stream() .map(orderMapper::toDomain) .collect(Collectors.toList()); } @Override public List<Order> findByStatus (OrderStatus status) { List<OrderPO> pos = jpaRepository.findByStatus(status.name()); return pos.stream() .map(orderMapper::toDomain) .collect(Collectors.toList()); } @Override public Optional<Order> findByIdWithItems (OrderId orderId) { return jpaRepository.findByIdWithItems(orderId.getValue()) .map(orderMapper::toDomain); } } public interface JpaOrderRepository extends JpaRepository <OrderPO, String> { List<OrderPO> findByUserId (String userId) ; List<OrderPO> findByStatus (String status) ; @Query("SELECT o FROM OrderPO o LEFT JOIN FETCH o.items WHERE o.id = :orderId") Optional<OrderPO> findByIdWithItems (@Param("orderId") String orderId) ; }
仓储的最佳实践 :
只为聚合根创建仓储,不为聚合内的实体创建仓储
仓储方法返回领域对象,而非数据模型
复杂查询可以使用CQRS模式,单独处理
7. 工厂(Factory) **工厂(Factory)**用于创建复杂的聚合或实体,封装创建逻辑。
何时使用工厂?
创建逻辑复杂
需要校验多个参数
创建过程涉及多个步骤
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 public class Order { public static Order create (UserId userId, List<OrderItem> items, Address shippingAddress) { Order order = new Order (); order.id = OrderId.generate(); order.userId = userId; order.items = items; order.shippingAddress = shippingAddress; order.totalAmount = calculateTotalAmount(items); order.status = OrderStatus.PENDING; return order; } } @Component public class OrderFactory { @Autowired private ProductRepository productRepository; @Autowired private PricingService pricingService; public Order createFromCart (UserId userId, ShoppingCart cart, Address shippingAddress) { List<OrderItem> orderItems = new ArrayList <>(); for (CartItem cartItem : cart.getItems()) { Product product = productRepository.findById(cartItem.getProductId()) .orElseThrow(() -> new ProductNotFoundException ()); OrderItem orderItem = new OrderItem ( product.getId(), product.getName(), product.getCurrentPrice(), cartItem.getQuantity() ); orderItems.add(orderItem); } Order order = Order.create(userId, orderItems, shippingAddress); Money basePrice = order.getTotalAmount(); Money finalPrice = pricingService.calculateFinalPrice(basePrice, userId); if (!finalPrice.equals(basePrice)) { Money discount = basePrice.subtract(finalPrice); order.applyDiscount(new FixedAmountDiscount (discount)); } return order; } public Order reconstruct (OrderId orderId, UserId userId, List<OrderItem> items, Money totalAmount, Address shippingAddress, OrderStatus status, LocalDateTime createdAt) { Order order = new Order (); order.setId(orderId); order.setUserId(userId); order.setItems(items); order.setTotalAmount(totalAmount); order.setShippingAddress(shippingAddress); order.setStatus(status); order.setCreatedAt(createdAt); return order; } }
DDD进阶模式 CQRS(命令查询职责分离) **CQRS(Command Query Responsibility Segregation)**将读操作(查询)和写操作(命令)分离,使用不同的模型处理。
为什么需要CQRS?
传统方式的问题:
1 2 3 4 5 6 7 8 9 Order order = orderRepository.findById(orderId); order.pay(paymentAmount); orderRepository.save(order);
CQRS方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 @Service @Transactional public class OrderCommandService { @Autowired private OrderRepository orderRepository; public void payOrder (PayOrderCommand command) { Order order = orderRepository.findById(command.getOrderId()) .orElseThrow(() -> new OrderNotFoundException ()); order.pay(command.getPaymentAmount()); orderRepository.save(order); DomainEventPublisher.publish(new OrderPaidEvent (order.getId(), command.getPaymentAmount())); } } @Service public class OrderQueryService { @Autowired private JdbcTemplate jdbcTemplate; public Page<OrderListDTO> queryOrderList (OrderQueryCriteria criteria) { String sql = """ SELECT o.id, o.user_id, o.total_amount, o.status, o.created_at, u.username, u.avatar FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE o.user_id = ? ORDER BY o.created_at DESC LIMIT ? OFFSET ? """ ; List<OrderListDTO> orders = jdbcTemplate.query(sql, new Object []{criteria.getUserId(), criteria.getPageSize(), criteria.getOffset()}, (rs, rowNum) -> { OrderListDTO dto = new OrderListDTO (); dto.setOrderId(rs.getString("id" )); dto.setTotalAmount(rs.getBigDecimal("total_amount" )); dto.setStatus(rs.getString("status" )); dto.setCreatedAt(rs.getTimestamp("created_at" ).toLocalDateTime()); dto.setUsername(rs.getString("username" )); dto.setUserAvatar(rs.getString("avatar" )); return dto; } ); return new Page <>(orders, criteria.getPageNumber(), criteria.getPageSize()); } public OrderDetailDTO queryOrderDetail (String orderId) { String sql = """ SELECT o.*, u.username, u.phone, p.name as product_name, p.image_url, oi.quantity, oi.unit_price, oi.subtotal FROM orders o LEFT JOIN users u ON o.user_id = u.id LEFT JOIN order_items oi ON o.id = oi.order_id LEFT JOIN products p ON oi.product_id = p.id WHERE o.id = ? """ ; } } CREATE TABLE order_query_view AS SELECT o.id as order_id, o.user_id, o.total_amount, o.status, o.created_at, u.username, u.phone, COUNT(oi.id) as item_count FROM orders o LEFT JOIN users u ON o.user_id = u.id LEFT JOIN order_items oi ON o.id = oi.order_id GROUP BY o.id; @Component public class OrderQueryModelUpdater { @Autowired private JdbcTemplate jdbcTemplate; @EventListener @Async public void handleOrderPaid (OrderPaidEvent event) { jdbcTemplate.update( "UPDATE order_query_view SET status = ? WHERE order_id = ?" , "PAID" , event.getOrderId().getValue() ); } }
CQRS的优势 :
性能优化 : 读写分离,各自优化
灵活查询 : 查询侧可以跨多个聚合
可扩展性 : 读写可以独立扩展
简化设计 : 领域模型专注于业务逻辑
Event Sourcing(事件溯源) Event Sourcing 不存储对象的当前状态,而是存储导致状态变化的所有事件,通过重放事件来重建对象状态。
传统方式 vs Event Sourcing :
1 2 3 4 5 6 7 8 9 10 11 12 13 | id | user_id | total_amount | status | created_at | |----|---------|--------------|--------|------------| | 1 | user1 | 100.00 | PAID | 2025 -01 -13 | | event_id | aggregate_id | event_type | event_data | occurred_at | |----------|--------------|-------------------|-------------------------------|-------------| | 1 | order1 | OrderCreated | {"userId" :"user1" ,"amount" :100 }| 2025 -01 -13 | | 2 | order1 | OrderPaid | {"amount" :100 } | 2025 -01 -13 | | 3 | order1 | OrderShipped | {"trackingNumber" :"123" } | 2025 -01 -14 |
简单实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 @Entity @Table(name = "domain_events") public class StoredEvent { @Id @GeneratedValue private Long id; private String aggregateId; private String eventType; @Column(columnDefinition = "TEXT") private String eventData; private LocalDateTime occurredAt; @Version private Long version; } public interface EventStore { void saveEvent (String aggregateId, DomainEvent event) ; List<DomainEvent> loadEvents (String aggregateId) ; } @Repository public class EventStoreImpl implements EventStore { @Autowired private JpaEventRepository jpaRepository; @Autowired private ObjectMapper objectMapper; @Override public void saveEvent (String aggregateId, DomainEvent event) { StoredEvent storedEvent = new StoredEvent (); storedEvent.setAggregateId(aggregateId); storedEvent.setEventType(event.getClass().getSimpleName()); storedEvent.setEventData(objectMapper.writeValueAsString(event)); storedEvent.setOccurredAt(event.getOccurredOn()); jpaRepository.save(storedEvent); } @Override public List<DomainEvent> loadEvents (String aggregateId) { List<StoredEvent> storedEvents = jpaRepository.findByAggregateIdOrderByOccurredAt(aggregateId); return storedEvents.stream() .map(this ::deserializeEvent) .collect(Collectors.toList()); } private DomainEvent deserializeEvent (StoredEvent storedEvent) { try { Class<? extends DomainEvent > eventClass = (Class<? extends DomainEvent >) Class.forName("com.example.domain.event." + storedEvent.getEventType()); return objectMapper.readValue(storedEvent.getEventData(), eventClass); } catch (Exception e) { throw new RuntimeException ("反序列化事件失败" , e); } } } public class Order { private OrderId id; private List<DomainEvent> uncommittedEvents = new ArrayList <>(); public static Order loadFromHistory (List<DomainEvent> history) { Order order = new Order (); for (DomainEvent event : history) { order.apply(event); } return order; } private void apply (DomainEvent event) { if (event instanceof OrderCreatedEvent) { OrderCreatedEvent e = (OrderCreatedEvent) event; this .id = e.getOrderId(); this .status = OrderStatus.PENDING; } else if (event instanceof OrderPaidEvent) { OrderPaidEvent e = (OrderPaidEvent) event; this .status = OrderStatus.PAID; } else if (event instanceof OrderShippedEvent) { OrderShippedEvent e = (OrderShippedEvent) event; this .status = OrderStatus.SHIPPED; } } public void pay (Money paymentAmount) { if (this .status != OrderStatus.PENDING) { throw new OrderException ("订单状态不允许支付" ); } OrderPaidEvent event = new OrderPaidEvent (this .id, paymentAmount); apply(event); uncommittedEvents.add(event); } public List<DomainEvent> getUncommittedEvents () { return new ArrayList <>(uncommittedEvents); } public void markEventsAsCommitted () { uncommittedEvents.clear(); } } @Repository public class EventSourcedOrderRepository implements OrderRepository { @Autowired private EventStore eventStore; @Override public Optional<Order> findById (OrderId orderId) { List<DomainEvent> events = eventStore.loadEvents(orderId.getValue()); if (events.isEmpty()) { return Optional.empty(); } Order order = Order.loadFromHistory(events); return Optional.of(order); } @Override public void save (Order order) { for (DomainEvent event : order.getUncommittedEvents()) { eventStore.saveEvent(order.getId().getValue(), event); } order.markEventsAsCommitted(); } }
Event Sourcing的优势 :
完整的审计日志 : 所有状态变化都有记录
时间旅行 : 可以重建任意时刻的状态
事件重放 : 可以用于调试和数据修复
自然支持CQRS : 事件可以投影到多个查询模型
Event Sourcing的挑战 :
查询复杂 : 需要重放事件或维护查询模型
事件版本管理 : 事件结构变化需要版本控制
存储成本 : 存储所有事件,数据量大
学习成本 : 思维模式转变
DDD最佳实践与常见误区 最佳实践 1. 如何识别聚合边界? 原则 :
一起变化的放在一起
考虑事务边界:一个事务只修改一个聚合
考虑并发性能:聚合越小,锁冲突越少
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ❌ 错误:聚合过大 Order (聚合根) ├── OrderItem ├── Payment ├── Invoice ├── Shipment └── Review 问题: - 订单支付和订单评价不需要在同一事务 - 并发修改冲突多 ✅ 正确:拆分为多个聚合 Order (聚合根) Payment (聚合根) ├── OrderItem Invoice (聚合根) Shipment (聚合根) Review (聚合根) 通过领域事件保持一致性: Order.pay() → OrderPaidEvent → ShipmentService.createShipment()
2. 如何设计值对象? 原则 :
优先使用值对象,而非基本类型
值对象必须不可变
值对象封装相关的业务逻辑
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Order { private BigDecimal amount; private String email; } public class Order { private Money amount; private Email email; } public class Email { private final String value; private Email (String value) { if (!value.matches("^[A-Za-z0-9+_.-]+@(.+)$" )) { throw new IllegalArgumentException ("邮箱格式不正确" ); } this .value = value; } public static Email of (String value) { return new Email (value); } public String getValue () { return value; } @Override public boolean equals (Object o) { if (this == o) return true ; if (!(o instanceof Email)) return false ; Email email = (Email) o; return Objects.equals(value, email.value); } @Override public int hashCode () { return Objects.hash(value); } }
3. 贫血模型 vs 充血模型 贫血模型(Anti-Pattern) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Order { private Long id; private BigDecimal amount; private String status; public Long getId () { return id; } public void setId (Long id) { this .id = id; } } @Service public class OrderService { public void payOrder (Order order, BigDecimal paymentAmount) { if (!order.getAmount().equals(paymentAmount)) { throw new Exception ("金额不匹配" ); } order.setStatus("PAID" ); orderRepository.save(order); } }
充血模型(DDD推荐) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Order { private OrderId id; private Money amount; private OrderStatus status; public void pay (Money paymentAmount) { if (!paymentAmount.equals(this .amount)) { throw new OrderException ("支付金额与订单金额不符" ); } if (this .status != OrderStatus.PENDING) { throw new OrderException ("订单状态不允许支付" ); } this .status = OrderStatus.PAID; DomainEventPublisher.publish(new OrderPaidEvent (this .id, paymentAmount)); } public OrderId getId () { return id; } public Money getAmount () { return amount; } public OrderStatus getStatus () { return status; } }
4. DDD与微服务的结合 原则 : 一个限界上下文 = 一个微服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 电商系统的微服务划分: 订单微服务 (Order Service) ├── 订单上下文 │ ├── Order (聚合根) │ └── OrderItem 商品微服务 (Product Service) ├── 商品上下文 │ ├── Product (聚合根) │ └── SKU 库存微服务 (Inventory Service) ├── 库存上下文 │ ├── Stock (聚合根) │ └── Warehouse 支付微服务 (Payment Service) ├── 支付上下文 │ └── Payment (聚合根) 会员微服务 (Member Service) ├── 会员上下文 │ ├── Member (聚合根) │ └── MemberLevel
服务间通信 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Service public class OrderApplicationService { @Autowired private RestTemplate restTemplate; public void createOrder (CreateOrderRequest request) { ProductDTO product = restTemplate.getForObject( "http://product-service/api/products/" + request.getProductId(), ProductDTO.class ); Order order = Order.create(...); orderRepository.save(order); eventPublisher.publish(new OrderCreatedEvent (order.getId())); } } @Component public class OrderEventPublisher { @Autowired private RabbitTemplate rabbitTemplate; public void publishOrderCreated (Order order) { OrderCreatedMessage message = new OrderCreatedMessage (); message.setOrderId(order.getId().getValue()); message.setUserId(order.getUserId().getValue()); message.setAmount(order.getTotalAmount().getAmount()); rabbitTemplate.convertAndSend("order.exchange" , "order.created" , message); } }
常见误区 1. 过度设计 误区 : 为简单的CRUD系统使用DDD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class User { private UserId id; private Email email; private PhoneNumber phone; } public class User { private Long id; private String email; private String phone; }
原则 : 只在复杂业务系统中使用DDD
2. 盲目追求纯粹的领域模型 误区 : 领域层完全不依赖任何框架
1 2 3 4 5 6 7 8 9 10 11 12 public class Order { } public class Order { }
原则 : 实用主义,平衡纯粹性和开发效率
3. 忽视性能考量 误区 : 每次都加载完整聚合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public List<OrderDTO> getOrderList (UserId userId) { List<Order> orders = orderRepository.findByUserId(userId); return orders.stream() .map(OrderDTO::from) .collect(Collectors.toList()); } public List<OrderListDTO> getOrderList (UserId userId) { return orderQueryService.queryOrderList(userId); }
原则 : 命令侧使用领域模型,查询侧使用CQRS优化
4. 与现有框架的冲突 误区 : DDD的聚合与JPA的实体映射冲突
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Entity public class Order { Order() {} public static Order create (...) { } } public class Order { } @Entity public class OrderPO { }
总结 DDD核心思想
以业务为中心 : 软件模型直接反映业务领域
统一语言 : 技术人员和业务人员使用相同的术语
分而治之 : 通过限界上下文划分复杂领域
充血模型 : 业务逻辑在领域对象内部
DDD的关键概念 战略设计 :
领域(Domain)
子域(Subdomain):核心域、支撑域、通用域
限界上下文(Bounded Context)
上下文映射(Context Map)
战术设计 :
实体(Entity):有唯一标识
值对象(Value Object):不可变,无标识
聚合(Aggregate):保证一致性的边界
领域服务(Domain Service):跨聚合的业务逻辑
领域事件(Domain Event):实现解耦和最终一致性
仓储(Repository):聚合的持久化
工厂(Factory):复杂对象的创建
DDD的适用场景 ✅ 适合使用DDD :
业务逻辑复杂,规则多变
需要长期维护的系统
团队规模较大,需要清晰的边界
微服务架构,需要划分服务边界
❌ 不适合使用DDD :
简单的CRUD应用
原型项目或短期项目
技术驱动型项目(如工具类系统)
团队对DDD理解不足
学习路径建议
理解核心概念 : 先掌握实体、值对象、聚合等基本概念
实践小项目 : 从简单的业务场景开始练习
阅读经典案例 : 学习开源项目中的DDD实践
与团队协作 : DDD强调统一语言,需要团队共识
持续优化 : 根据业务变化持续重构领域模型
参考资料
相关文章 :