DDD领域驱动设计SKILLS手册
DDD领域驱动设计SKILLS手册
·
DDD领域驱动设计SKILLS手册
版本: v1.0
适用: 后端架构设计与开发
目标: 统一团队DDD实践标准,提升代码质量与业务表达力
文章目录
1. 核心原则(Core Principles)
1.1 领域优先原则
❌ 错误:先设计数据库表结构,再写业务代码
✅ 正确:先理解业务领域,建立领域模型,持久化是细节
关键问题:
- 这个业务概念是什么?(实体还是值对象?)
- 它的业务行为是什么?(方法名应该是业务动词)
- 业务规则/不变量是什么?(在领域层强制保证)
1.2 边界清晰原则
每个限界上下文(Bounded Context)必须:
- 有明确的业务边界和通用语言
- 独立部署和演进(微服务粒度参考)
- 对外通过防腐层(ACL)交互,禁止直接引用内部模型
1.3 富模型原则
❌ 贫血模型:Entity只有getter/setter,逻辑在Service中
✅ 富模型:Entity封装业务逻辑和状态转换
判断标准:
如果Service只是"调用entity.getXxx()然后setXxx()",说明模型贫血
2. 分层规范(Layer Standards)
2.1 四层架构职责矩阵
| 层级 | 职责 | 允许 | 禁止 |
|---|---|---|---|
| 用户界面层 (Interfaces) |
协议适配、数据转换、输入校验 | DTO、Controller、Validator | 业务逻辑、事务控制 |
| 应用层 (Application) |
用例编排、事务边界、事件发布 | ApplicationService、Command/Query、DTO | 领域规则、持久化细节 |
| 领域层 (Domain)★ |
业务逻辑、规则校验、状态管理 | Entity、ValueObject、DomainService、Repository接口、DomainEvent | 依赖Spring、数据库访问、外部API调用 |
| 基础设施层 (Infrastructure) |
技术实现、持久化、消息、外部调用 | RepositoryImpl、MessagePublisher、Config、外部Client | 业务逻辑 |
2.2 依赖规则(强制)
依赖方向:Interfaces → Application → Domain ← Infrastructure
关键约束:
1. 领域层无任何外部依赖(纯净Java/Kotlin)
2. 上层通过接口调用下层,禁止跨层调用
3. 基础设施层通过依赖倒置(DIP)接入领域层
2.3 包结构规范
com.company.project.
├── interfaces/ # 用户界面层
│ ├── rest/ # REST API
│ │ ├── OrderController.java
│ │ └── dto/ # Request/Response DTO
│ ├── mq/ # 消息消费者
│ └── scheduler/ # 定时任务
│
├── application/ # 应用层
│ ├── service/ # 应用服务
│ │ ├── OrderApplicationService.java
│ │ └── OrderQueryService.java
│ ├── command/ # 命令对象(写操作)
│ │ ├── CreateOrderCommand.java
│ │ └── PayOrderCommand.java
│ ├── query/ # 查询对象(读操作,CQRS)
│ │ └── OrderSummaryQuery.java
│ ├── dto/ # 应用层DTO
│ └── eventhandler/ # 领域事件处理器
│
├── domain/ # 领域层 ★核心
│ ├── order/ # 聚合包(按聚合组织)
│ │ ├── Order.java # 聚合根(实体)
│ │ ├── OrderItem.java # 聚合内实体
│ │ ├── OrderStatus.java # 值对象(枚举)
│ │ ├── OrderRepository.java # 仓储接口
│ │ ├── OrderDomainService.java # 领域服务(可选)
│ │ └── event/ # 领域事件
│ │ ├── OrderCreatedEvent.java
│ │ └── OrderPaidEvent.java
│ ├── shared/ # 共享内核
│ │ ├── Money.java # 通用值对象
│ │ └── AggregateRoot.java # 基类
│ └── service/ # 跨聚合领域服务
│
└── infrastructure/ # 基础设施层
├── repository/ # 仓储实现
│ ├── OrderRepositoryImpl.java
│ └── jpa/ # ORM映射
├── messaging/ # 消息实现
├── external/ # 外部服务防腐层
│ ├── payment/
│ │ ├── PaymentGateway.java # 接口(领域层定义)
│ │ └── AlipayGatewayAdapter.java # 实现
└── config/ # 配置类
3. 战术设计规范(Tactical Patterns)
3.1 实体(Entity)规范
/**
* 实体规范检查清单:
* □ 有唯一标识(ID),使用值对象包装(非String/Long裸类型)
* □ 构造函数私有或保护,通过工厂方法创建
* □ 业务方法命名使用业务动词(非CRUD词汇)
* □ 状态变更通过方法封装,禁止public setter
* □ 校验逻辑在构造和方法中,保持有效状态
* □ 关联对象仅引用聚合根ID(非直接对象引用,防内存泄漏)
*/
public class Order extends AggregateRoot<OrderId> {
// 1. 标识:使用值对象
private OrderId id;
// 2. 基本属性:值对象或基本类型
private OrderStatus status;
private Money totalAmount;
private LocalDateTime createTime;
// 3. 聚合内实体:通过ID引用其他聚合(非直接对象)
private CustomerId customerId;
// 4. 聚合内组件:直接持有(生命周期同步)
private List<OrderItem> items = new ArrayList<>();
// 5. 领域事件:内部收集,外部发布
private List<DomainEvent> domainEvents = new ArrayList<>();
// 构造函数私有,强制走工厂
private Order() {}
// 工厂方法:封装复杂创建逻辑
public static Order create(CustomerId customerId, List<OrderItem> items) {
Order order = new Order();
order.id = OrderId.generate();
order.customerId = customerId;
order.status = OrderStatus.PENDING;
order.createTime = LocalDateTime.now();
// 业务规则校验
if (items == null || items.isEmpty()) {
throw new DomainException("Order must have at least one item");
}
// 添加项并计算总额
items.forEach(order::addItem);
// 注册领域事件
order.registerEvent(new OrderCreatedEvent(order.id));
return order;
}
// 业务方法:使用业务动词,封装状态转换
public void pay(Money payment) {
// 状态校验(不变量保护)
if (status != OrderStatus.PENDING) {
throw new DomainException("Only pending order can be paid, current: " + status);
}
// 业务规则校验
if (!payment.equals(this.totalAmount)) {
throw new DomainException("Payment amount must equal total amount");
}
// 状态转换
this.status = OrderStatus.PAID;
this.payTime = LocalDateTime.now();
// 注册事件
registerEvent(new OrderPaidEvent(this.id, payment));
}
public void cancel(String reason) {
if (status == OrderStatus.SHIPPED || status == OrderStatus.DELIVERED) {
throw new DomainException("Cannot cancel shipped or delivered order");
}
this.status = OrderStatus.CANCELLED;
this.cancelReason = reason;
registerEvent(new OrderCancelledEvent(this.id, reason));
}
// 内部方法:添加项时维护总额一致性
private void addItem(OrderItem item) {
this.items.add(item);
recalculateTotal();
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::getSubTotal)
.reduce(Money.ZERO, Money::add);
}
// 禁止直接修改状态
// ❌ public void setStatus(OrderStatus status) { this.status = status; }
}
3.2 值对象(Value Object)规范
/**
* 值对象规范:
* □ 不可变(final字段,无setter)
* □ 基于属性判等(重写equals/hashCode)
* □ 可共享(无副作用)
* □ 行为封装(如Money的加减运算)
*/
public record Money(BigDecimal amount, Currency currency) {
public static final Money ZERO = new Money(BigDecimal.ZERO, Currency.CNY);
// 校验在构造时
public Money {
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
throw new DomainException("Amount must be positive");
}
if (currency == null) {
throw new DomainException("Currency required");
}
// 不可变,创建时确定精度
amount = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
}
// 业务行为方法(非getter/setter)
public Money add(Money other) {
checkCurrencyMatch(other);
return new Money(this.amount.add(other.amount), this.currency);
}
public Money subtract(Money other) {
checkCurrencyMatch(other);
if (other.amount.compareTo(this.amount) > 0) {
throw new DomainException("Insufficient amount");
}
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 boolean isGreaterThan(Money other) {
checkCurrencyMatch(other);
return this.amount.compareTo(other.amount) > 0;
}
private void checkCurrencyMatch(Money other) {
if (!this.currency.equals(other.currency)) {
throw new DomainException("Currency mismatch: " + this.currency + " vs " + other.currency);
}
}
// 显示格式(非业务逻辑,但方便调试)
@Override
public String toString() {
return currency.getSymbol() + amount.toString();
}
}
// 其他值对象示例
public record Address(String province, String city, String detail, String zipCode) {
public Address {
if (StringUtils.isBlank(province) || StringUtils.isBlank(city)) {
throw new DomainException("Province and city required");
}
}
public String fullAddress() {
return province + " " + city + " " + detail;
}
}
3.3 聚合(Aggregate)规范
/**
* 聚合设计原则:
* 1. 边界内强一致性,边界外最终一致性
* 2. 聚合根唯一对外引用入口
* 3. 聚合内对象通过ID引用其他聚合(非对象引用)
* 4. 小聚合优先(性能考虑,大聚合影响并发)
* 5. 一个事务只修改一个聚合(除非特殊情况)
*/
public class Order extends AggregateRoot<OrderId> {
// 聚合根ID
private OrderId id;
// 聚合内实体:直接引用(生命周期同步)
private List<OrderItem> items;
private ShippingAddress shippingAddress;
// 关联聚合:仅存ID(防内存泄漏,解耦)
private CustomerId customerId;
private PaymentId paymentId; // 支付聚合ID
// 聚合内实体修改必须通过聚合根
public void changeShippingAddress(ShippingAddress newAddress) {
if (status != OrderStatus.PENDING) {
throw new DomainException("Cannot change address for non-pending order");
}
this.shippingAddress = newAddress;
registerEvent(new ShippingAddressChangedEvent(this.id, newAddress));
}
// 批量操作保持业务一致性
public void confirmShipment(TrackingNumber trackingNumber) {
if (status != OrderStatus.PAID) {
throw new DomainException("Only paid order can be shipped");
}
this.status = OrderStatus.SHIPPED;
this.trackingNumber = trackingNumber;
this.shipTime = LocalDateTime.now();
// 同时更新所有项状态(一致性)
this.items.forEach(OrderItem::markAsShipped);
registerEvent(new OrderShippedEvent(this.id, trackingNumber));
}
}
// 聚合内实体(无全局ID,只有局部ID)
public class OrderItem {
private Long localId; // 仅数据库主键,业务无意义
private ProductId productId; // 关联其他聚合,存ID
private String productName; // 冗余快照(防关联变化)
private Money price; // 下单时价格快照
private int quantity;
void markAsShipped() {
this.status = ItemStatus.SHIPPED;
}
Money getSubTotal() {
return price.multiply(quantity);
}
}
3.4 领域服务(Domain Service)规范
/**
* 领域服务使用场景:
* □ 跨实体的业务逻辑(无单一归属)
* □ 需要访问基础设施的领域逻辑(如计算运费需查地区表)
* □ 复杂计算或策略逻辑
*
* 避免滥用:先尝试放在实体中,确实不适合再放服务
*/
public interface PricingService {
Money calculateOrderTotal(List<OrderItem> items, CustomerLevel level, Coupon coupon);
}
// 实现可放在领域层(纯计算)或基础设施层(需查表)
public class PricingServiceImpl implements PricingService {
private final CustomerDiscountPolicy discountPolicy;
private final CouponService couponService; // 另一个领域服务
@Override
public Money calculateOrderTotal(List<OrderItem> items, CustomerLevel level, Coupon coupon) {
Money subTotal = items.stream()
.map(OrderItem::getSubTotal)
.reduce(Money.ZERO, Money::add);
Money discount = discountPolicy.calculateDiscount(subTotal, level);
Money couponDiscount = couponService.applyCoupon(subTotal.subtract(discount), coupon);
return subTotal.subtract(discount).subtract(couponDiscount);
}
}
// 应用层调用
@Service
@RequiredArgsConstructor
public class OrderApplicationService {
private final PricingService pricingService; // 注入领域服务
public OrderId createOrder(CreateOrderCommand cmd) {
// ... 准备items
Money total = pricingService.calculateOrderTotal(items, customer.getLevel(), cmd.getCoupon());
Order order = Order.create(customerId, items, total);
// ...
}
}
3.5 仓储(Repository)规范
/**
* 仓储规范:
* □ 领域层定义接口(Repository),基础设施实现(RepositoryImpl)
* □ 按聚合根设计,一个聚合一个仓储
* □ 接口方法使用领域对象(非数据库实体)
* □ 支持通过ID查询、保存、删除
* □ 复杂查询用Specification模式或QueryService(CQRS)
*/
// ========== 领域层:接口定义 ==========
public interface OrderRepository {
Order findById(OrderId id); // 返回聚合根
List<Order> findByCustomerId(CustomerId customerId, TimeRange range);
Order save(Order order); // 保存聚合(含内部实体)
void delete(OrderId id);
// 特殊查询:返回ID列表(避免大对象)
List<OrderId> findPendingOrdersBefore(LocalDateTime deadline);
}
// ========== 基础设施层:实现 ==========
@Repository
@RequiredArgsConstructor
public class OrderRepositoryImpl implements OrderRepository {
private final OrderJpaRepository jpaRepository; // Spring Data
private final OrderDataConverter converter; // 领域对象<->PO转换
@Override
public Order findById(OrderId id) {
OrderPO po = jpaRepository.findById(id.getValue())
.orElseThrow(() -> new EntityNotFoundException("Order not found: " + id));
return converter.toDomain(po);
}
@Override
public Order save(Order order) {
OrderPO po = converter.toPO(order);
OrderPO saved = jpaRepository.save(po);
return converter.toDomain(saved);
}
// 复杂查询用原生SQL或QueryDSL,但返回领域对象
@Override
public List<Order> findByCustomerId(CustomerId customerId, TimeRange range) {
List<OrderPO> pos = jpaRepository.findByCustomerIdAndCreateTimeBetween(
customerId.getValue(), range.getStart(), range.getEnd());
return pos.stream().map(converter::toDomain).collect(Collectors.toList());
}
}
// CQRS:复杂查询分离到QueryService(不经过领域模型)
@Service
public class OrderQueryService {
@Query("SELECT new com.example.OrderSummary(o.id, o.status, o.totalAmount) ...")
public List<OrderSummary> searchOrders(OrderSearchCriteria criteria) {
// 直接查数据库,返回DTO,不构造领域对象
}
}
3.6 领域事件(Domain Event)规范
/**
* 领域事件规范:
* □ 命名:过去时态(OrderCreated, PaymentCompleted)
* □ 包含事件ID、时间戳、来源聚合ID
* □ 只包含必要数据(ID+关键属性),避免大对象
* □ 在聚合内注册,应用层发布
* □ 处理异步,保证最终一致性
*/
public class OrderPaidEvent implements DomainEvent {
private final EventId eventId;
private final LocalDateTime occurredOn;
private final OrderId orderId;
private final Money paymentAmount;
private final LocalDateTime payTime;
public OrderPaidEvent(OrderId orderId, Money paymentAmount) {
this.eventId = EventId.generate();
this.occurredOn = LocalDateTime.now();
this.orderId = orderId;
this.paymentAmount = paymentAmount;
this.payTime = LocalDateTime.now();
}
// getter...
}
// 聚合内注册
public class Order extends AggregateRoot<OrderId> {
private List<DomainEvent> domainEvents = new ArrayList<>();
protected void registerEvent(DomainEvent event) {
this.domainEvents.add(event);
}
public List<DomainEvent> getDomainEvents() {
return Collections.unmodifiableList(domainEvents);
}
public void clearEvents() {
this.domainEvents.clear();
}
}
// 应用层发布(事务提交后)
@Service
public class OrderApplicationService {
@Transactional
public void payOrder(PayOrderCommand cmd) {
Order order = orderRepository.findById(cmd.getOrderId());
order.pay(cmd.getPayment());
orderRepository.save(order);
// 事务提交后发布(避免事务内发布但后续回滚)
transactionTemplate.afterCommit(() -> {
eventPublisher.publish(order.getDomainEvents());
order.clearEvents();
});
}
}
// 事件处理器(应用层或单独进程)
@Component
public class OrderEventHandler {
@EventListener
public void onOrderPaid(OrderPaidEvent event) {
// 发送通知
notificationService.sendPaymentConfirmation(event.getOrderId());
// 触发物流(另一个限界上下文)
logisticsContextClient.createShipment(event.getOrderId());
}
@EventListener
public void onOrderPaid_UpdateStats(OrderPaidEvent event) {
// 更新统计(另一个上下文,最终一致性)
statsService.incrementDailyRevenue(event.getPaymentAmount());
}
}
4. 代码组织规范(Code Organization)
4.1 模块划分策略
策略A:按限界上下文划分(推荐微服务)
├── order-context/ # 订单上下文(独立服务)
├── payment-context/ # 支付上下文
├── inventory-context/ # 库存上下文
└── shared-kernel/ # 共享内核(通用语言、基础类型)
策略B:单体应用内的模块化(Package by Layer → Package by Feature)
├── com.example.ecommerce.
│ ├── order/ # 订单模块(按业务功能组织)
│ │ ├── application/
│ │ ├── domain/
│ │ └── infrastructure/
│ ├── payment/
│ └── shared/
4.2 依赖管理(Gradle示例)
// 领域层:零依赖(或仅工具库)
project(':domain') {
dependencies {
implementation 'org.apache.commons:commons-lang3'
// ❌ 禁止:spring-boot, jpa, jackson等
}
}
// 应用层:依赖领域层
project(':application') {
dependencies {
implementation project(':domain')
implementation 'org.springframework:spring-tx' // 仅事务
}
}
// 基础设施层:依赖所有上层,实现接口
project(':infrastructure') {
dependencies {
implementation project(':domain')
implementation project(':application')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
}
5. 命名规范(Naming Conventions)
5.1 分层命名
| 层级 | 类名后缀 | 示例 |
|---|---|---|
| 用户界面 | Controller, Resource, Handler |
OrderController, OrderEventHandler |
| 应用服务 | ApplicationService, QueryService |
OrderApplicationService |
| 领域服务 | Service, Policy, Calculator |
PricingService, DiscountPolicy |
| 仓储接口 | Repository |
OrderRepository |
| 仓储实现 | RepositoryImpl, Mapper |
OrderRepositoryImpl, OrderMapper |
| 领域事件 | Event(过去时) |
OrderCreatedEvent |
| 命令对象 | Command |
CreateOrderCommand |
| 查询对象 | Query, Criteria |
OrderSummaryQuery |
| DTO | DTO, VO(视图对象) |
OrderDTO, OrderVO |
5.2 方法命名
// 应用层:业务用例(动词+名词)
public OrderId createOrder(CreateOrderCommand cmd);
public void cancelOrder(OrderId id, String reason);
public List<OrderSummary> queryOrderHistory(CustomerId id);
// 领域层:业务行为(业务动词)
order.pay(money); // 支付
order.confirmShipment(); // 确认发货
order.applyCoupon(coupon); // 应用优惠券
order.archive(); // 归档
// ❌ 避免:CRUD词汇
order.updateStatus(); // 太泛,无业务含义
order.setPaid(); // 只是状态设置,无过程
order.save(); // 持久化操作,非领域行为
6. 常见反模式(Anti-Patterns)
❌ 反模式1:贫血领域模型
// 错误:Entity只有数据,逻辑在Service中
public class Order {
private Long id;
private String status; // String而非枚举
// 只有getter/setter...
}
@Service
public class OrderService {
public void pay(Long orderId) {
Order order = orderDao.findById(orderId);
if (!"PENDING".equals(order.getStatus())) { // 业务规则散落
throw new RuntimeException("Invalid status");
}
order.setStatus("PAID"); // 直接set,无封装
orderDao.update(order);
}
}
❌ 反模式2:跨聚合直接引用
// 错误:直接持有其他聚合根的对象引用
public class Order {
private Customer customer; // ❌ 应该存CustomerId
private List<Product> products; // ❌ 应该存ProductId+快照
}
// 正确:通过ID引用,需要时通过仓储查询
public class Order {
private CustomerId customerId;
private List<OrderItem> items; // OrderItem内包含productId和快照
}
❌ 反模式3:领域层依赖基础设施
// 错误:Entity依赖Spring或数据库
@Entity // ❌ JPA注解在领域层(污染)
public class Order {
@Autowired // ❌ 禁止在领域层使用Spring
private PriceService priceService;
public void calculate() {
redisTemplate.get(...); // ❌ 直接访问Redis
}
}
❌ 反模式4:万能聚合
// 错误:聚合过大,包含所有关联对象
public class Order {
private List<OrderItem> items;
private Payment payment; // ❌ 应该是PaymentId
private Shipment shipment; // ❌ 应该是ShipmentId
private Invoice invoice; // ❌ 应该是InvoiceId
private List<Comment> comments; // ❌ 单独聚合
// ... 几十个子对象
}
// 问题:加载慢、并发冲突、事务范围过大
❌ 反模式5:绕过应用层
// 错误:Controller直接调用Repository
@RestController
public class OrderController {
@Autowired
private OrderRepository repo; // ❌ 应通过ApplicationService
@PostMapping("/orders")
public Order create(@RequestBody Order order) { // ❌ 直接暴露领域对象
return repo.save(order);
}
}
7. 检查清单(Checklist)
设计阶段
- 与业务专家完成事件风暴,识别聚合和限界上下文
- 定义通用语言,建立词汇表(Glossary)
- 绘制上下文映射图,明确集成关系
- 识别核心域、支撑域、通用域,分配资源优先级
编码阶段
- 领域层代码不依赖Spring/JPA等框架
- 实体使用业务ID(值对象),非数据库自增ID
- 所有业务规则在领域层校验(构造、方法中)
- 应用服务无if/switch业务判断,仅编排
- 跨聚合调用通过ID+仓储,或领域事件
- 复杂查询使用CQRS,不污染领域模型
代码审查
- 方法名是否反映业务操作(非技术操作)?
- 是否避免了getter/setter泛滥?
- 事务边界是否合理(一个事务一个聚合)?
- 领域事件是否最终一致性处理?
- 防腐层是否隔离了外部系统变化?
📚 参考资源
- 《领域驱动设计》 - Eric Evans(蓝皮书,理论奠基)
- 《实现领域驱动设计》 - Vaughn Vernon(红皮书,实践指南)
- 《领域驱动设计精粹》 - Vaughn Vernon(快速入门)
更多推荐


所有评论(0)