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泛滥?
  • 事务边界是否合理(一个事务一个聚合)?
  • 领域事件是否最终一致性处理?
  • 防腐层是否隔离了外部系统变化?

📚 参考资源

  1. 《领域驱动设计》 - Eric Evans(蓝皮书,理论奠基)
  2. 《实现领域驱动设计》 - Vaughn Vernon(红皮书,实践指南)
  3. 《领域驱动设计精粹》 - Vaughn Vernon(快速入门)

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐