【DDD】编码实战

参考:https://insights.thoughtworks.cn/backend-development-ddd/  后端开发实践系列——领域驱动设计(DDD)编码实践


战略设计更偏向于软件架构,那么战术设计便更偏向于编码实现。DDD战术设计的目的是使得业务能够从技术中分离并突显出来,让代码直接表达业务的本身,其中包含了聚合根、应用服务、资源库、工厂等概念。

源码参考: https://github.com/e-commerce-sample/ecommerce-order-service


实现业务的3种方式对比

1、service+贫血模型存在一个贫血的“领域对象”,业务逻辑通过一个Service类实现,然后通过setter方法更新领域对象,最后通过DAO(多数情况下可能使用诸如Hibernate之类的ORM框架)保存到数据库中。

     职责划分模糊不清,使本应该内聚在Order中的业务逻辑泄露到了其他地方(OrderService),导致Order成为一个只是充当数据容器的贫血模型(Anemic Model),而非真正意义上的领域模型。在项目持续演进的过程中,这些业务逻辑会分散在不同的Service类中,最终的结果是代码变得越来越难以理解进而逐渐丧失扩展能力。

@Transactional
public void changeProductCount(String id, ChangeProductCountCommand command) {
    Order order = DAO.findById(id);
    if (order.getStatus() == PAID) {
        throw new OrderCannotBeModifiedException(id);
    }
    OrderItem orderItem = order.getOrderItem(command.getProductId());
    orderItem.setCount(command.getCount());
    order.setTotalPrice(calculateTotalPrice(order));
    DAO.saveOrUpdate(order);
}

2、事务脚本在不使用ORM的情况下,领域对象甚至都没有必要存在。于是,此时的代码实现便退化成了事务脚本(Transaction Script),也就是直接将Service类中计算出的结果直接保存到数据库(或者有时都没有Service类,直接通过SQL实现业务逻辑):

@Transactional
public void changeProductCount(String id, ChangeProductCountCommand command) {
    OrderStatus orderStatus = DAO.getOrderStatus(id);
    if (orderStatus == PAID) {
        throw new OrderCannotBeModifiedException(id);
    }
    DAO.updateProductCount(id, command.getProductId(), command.getCount());
    DAO.updateTotalPrice(id);
}

3、DDD核心的业务逻辑被内聚在行为饱满的领域对象

class Order {
    public void changeProductCount(ProductId productId, int count) {
        if (this.status == PAID) {
            throw new OrderCannotBeModifiedException(this.id);
        }
        OrderItem orderItem = retrieveItem(productId);
        orderItem.updateCount(count);
    }
}

// Controller
@PostMapping("/order/{id}/products")
public void changeProductCount(@PathVariable(name = "id") String id, @RequestBody @Valid ChangeProductCountCommand command) {
    Order order = DAO.byId(orderId(id));
    order.changeProductCount(ProductId.productId(command.getProductId()), command.getCount());
    order.updateTotalPrice();
    DAO.saveOrUpdate(order);
}

 目录划分

基于聚合根进行顶层包的划分:“内聚性”原则的典型代表,在各自的顶层包下再根据代码结构的复杂程度划分子包

如果子包下只有单个文件,足够简单,可以不创建子包

├── order
│   ├── OrderApplicationService.java
│   ├── OrderController.java
│   ├── OrderPaymentProxy.java
│   ├── OrderPaymentService.java
│   ├── OrderRepository.java
│   ├── command
│   │   ├── ChangeAddressDetailCommand.java
│   │   ├── CreateOrderCommand.java
│   │   ├── OrderItemCommand.java
│   │   ├── PayOrderCommand.java
│   │   └── UpdateProductCountCommand.java
│   ├── exception
│   │   ├── OrderCannotBeModifiedException.java
│   │   ├── OrderNotFoundException.java
│   │   ├── PaidPriceNotSameWithOrderPriceException.java
│   │   └── ProductNotInOrderException.java
│   ├── model
│   │   ├── Order.java
│   │   ├── OrderFactory.java
│   │   ├── OrderId.java
│   │   ├── OrderIdGenerator.java
│   │   ├── OrderItem.java
│   │   └── OrderStatus.java
│   └── representation
│       ├── OrderItemRepresentation.java
│       ├── OrderRepresentation.java
│       └── OrderRepresentationService.java

除上述之外,可创建单独的common类(下含util/log/xxx)等

原文地址:https://www.cnblogs.com/clarino/p/15510589.html