战术领域驱动设计:将战略融入代码实现

本文深入探讨战术领域驱动设计的七大核心模式,包括实体、值对象、聚合、仓储等,通过具体Java代码示例展示如何将业务概念转化为可维护的软件实现,帮助开发者构建真正反映业务领域的系统。

战术领域驱动设计:将战略融入代码

在之前的文章中,我讨论了领域驱动设计中最常被忽视的方面:战略层面。在软件开发中,团队往往急于编写代码,认为实现会澄清领域。历史表明情况恰恰相反——在没有理解根本原因或方向的情况下构建系统,通常会导致系统在技术上是正确的,但在概念上是错误的。

既然我们已经探讨了“为什么”和“什么”,现在该转向“如何”了。战术DDD代表了这一下一步——将充分理解的领域转化为表达性强、可维护代码的过程。虽然战略设计定义边界并促进共享理解,但战术设计在这些边界内将想法变为现实。

战术DDD专注于实现领域模型。它提供了丰富的设计模式词汇——实体、值对象、聚合、仓储和领域服务——每个模式在表达业务逻辑方面都有精确的用途。

使战术DDD至关重要的一点是它能够创建不仅准确而且能够适应变化的模型。没有良好的方向,我们可能会误导并产生不必要的复杂性。这一层将战略模型的概念清晰性与实现的实际需求连接起来。

战术设计由七个基本模式组成,将概念转化为代码:

  • 实体 - 具有身份的对象,持久存在并不断演变
  • 值对象 - 仅由其属性定义的不可变对象
  • 聚合 - 确保一致边界的相关实体组
  • 仓储 - 抽象聚合持久化的接口
  • 工厂 - 负责创建复杂的领域对象
  • 领域服务 - 包含不适合实体或值对象的领域逻辑
  • 领域事件 - 捕获并传达领域中重要的发生事件

实体

实体代表具有唯一身份或ID的领域对象,即使其属性可能发生变化,这些对象也会随时间持续存在。它们模拟连续性——即使数据演变,某些东西保持不变。

1
2
3
4
5
6
7
8
9
public class Order {
    private final UUID orderId;
    private List<OrderItem> items = new ArrayList<>();
    private OrderStatus status = OrderStatus.NEW;
    
    public void addItem(OrderItem item) {
        items.add(item);
    }
}

值对象

值对象描述完全由其值而非身份定义的领域元素。它们是不可变的、可替换的,并通过内容确保相等性。

1
2
3
4
5
6
7
8
public record Money(BigDecimal amount, String currency) {
    public Money add(Money other) {
        if (!currency.equals(other.currency())) {
            throw new IllegalArgumentException("Currencies must match");
        }
        return new Money(amount.add(other.amount()), currency);
    }
}

聚合

聚合在单个一致性边界下组织相关实体和值对象,确保业务规则保持有效。聚合根充当其内部状态的守护者。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Order {
    private final UUID orderId;
    private final List<OrderItem> items = new ArrayList<>();
    
    public void addItem(Product product, int quantity) {
        items.add(new OrderItem(product, quantity));
    }
    
    public BigDecimal total() {
        return items.stream()
                   .map(OrderItem::subtotal)
                   .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

仓储

仓储抽象了聚合的存储和检索方式,使领域独立于数据库关注点。它们充当处理持久性的内存集合。

1
2
3
4
5
public interface OrderRepository {
    Optional<Order> findById(UUID id);
    void save(Order order);
    void delete(Order order);
}

工厂

工厂负责创建复杂的领域对象,同时确保所有不变量得到满足。它们集中创建逻辑,使实体免于构建复杂性。

1
2
3
4
5
6
7
public class OrderFactory {
    public Order create(Customer customer, List<Product> products) {
        Order order = new Order(UUID.randomUUID(), customer);
        products.forEach(p -> order.addItem(p, 1));
        return order;
    }
}

领域服务

领域服务包含不适合实体或值对象的领域逻辑。它们表达涉及多个聚合或横切业务规则的行为。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class PaymentService {
    private final PaymentGateway gateway;
    
    public PaymentService(PaymentGateway gateway) {
        this.gateway = gateway;
    }
    
    public PaymentReceipt processPayment(Order order, Money amount) {
        return gateway.charge(order.getOrderId(), amount);
    }
}

领域事件

领域事件捕获业务领域内有意义的发生事件。它们代表发生的事情——不是外部触发器,而是领域本身想要分享的事实。

1
2
3
4
5
public record OrderPlacedEvent(UUID orderId, Instant occurredAt) {
    public static OrderPlacedEvent from(Order order) {
        return new OrderPlacedEvent(order.getOrderId(), Instant.now());
    }
}

应用服务——编排用例

虽然应用服务不是原始七个战术DDD模式的一部分,但它们在现代架构中的作用值得提及。它们充当用例编排器,协调领域操作而不包含业务逻辑本身。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class OrderApplicationService {
    private final OrderRepository repository;
    private final PaymentService paymentService;
    private final OrderFactory factory;
    
    public OrderApplicationService(OrderRepository repository,
                                   PaymentService paymentService,
                                   OrderFactory factory) {
        this.repository = repository;
        this.paymentService = paymentService;
        this.factory = factory;
    }
    
    @Transactional
    public void placeOrder(Customer customer, List<Product> products) {
        Order order = factory.create(customer, products);
        repository.save(order);
        paymentService.processPayment(order, order.total());
    }
}

在实践中,应用服务作为用例的入口点,管理事务、调用领域逻辑并根据需要触发外部集成。它们保持模型的纯粹性,同时使系统能够端到端执行连贯的业务流程。

结论

战术领域驱动设计将战略变为现实。虽然战略方面定义边界和共享理解,但战术模式——实体、值对象、聚合、仓储、工厂、领域服务和领域事件——将该愿景转化为表达性强、可维护的代码。即使应用服务不是原始七个模式的一部分,它在编排用例和维护模型纯粹性方面也发挥着至关重要的作用。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计