什么是领域模型(domain model)?贫血模型(anaemic domain model)和充血模型(Rich Domain Model)

1,485 阅读4分钟

领域模型(Domain Model)

领域模型是软件工程中的一个概念,指的是在特定业务领域内,对现实世界中的实体、概念、业务规则和关系的抽象表达。它是应用程序设计的核心部分,用于指导软件系统的开发,并作为业务专家和软件开发人员之间沟通的桥梁。

领域模型通常包含以下元素:

  • 实体(Entities):代表现实世界中的一个对象,如用户、订单等。
  • 值对象(Value Objects):描述实体的属性,通常是不可变的,如日期、金额等。
  • 聚合(Aggregates):一组相关对象的集合,它们一起作为一个单元进行操作。
  • 服务(Services):执行领域逻辑的操作,通常与特定的实体或聚合无关。
  • 领域事件(Domain Events):领域内发生的有意义的事件,通常触发进一步的处理。

贫血模型(Anaemic Domain Model)

贫血模型是一种反模式,指的是领域模型中的实体仅仅包含数据和访问这些数据的getter/setter方法,而缺乏业务逻辑和行为。在这种模型中,实体变成了简单的数据容器,所有的业务逻辑都集中在服务层或控制器中。

特点:

  • 实体中没有业务逻辑。
  • 实体只包含属性和访问这些属性的方法。
  • 业务逻辑通常在服务层或控制器中实现。

充血模型(Rich Domain Model)

充血模型是一种更加成熟的领域模型设计方法,它强调将业务逻辑和行为封装在实体内部。在这种模型中,实体不仅仅是数据的容器,它们还包含行为和业务规则,能够自主管理自己的状态。

特点:

  • 实体包含业务逻辑和行为。
  • 实体能够根据业务规则改变其状态。
  • 服务层通常很薄,主要负责协调实体之间的交互。
  • 更容易实现复杂的业务规则和领域逻辑。

区别

  • 业务逻辑的位置:贫血模型中,业务逻辑通常位于服务层或控制器;而充血模型中,业务逻辑封装在实体内部。
  • 实体的职责:在贫血模型中,实体仅作为数据的载体;在充血模型中,实体具有完整的业务行为和决策能力。
  • 服务层的厚度:贫血模型中,服务层可能很厚,包含大量业务逻辑;充血模型中,服务层较薄,主要负责流程和协调。
  • 可维护性和可测试性:充血模型通常更易于维护和测试,因为业务逻辑集中在领域模型中,而不是分散在应用程序的各个部分。

在现代软件架构中,充血模型被认为是一种更优的设计方法,因为它更好地遵循了面向对象的设计原则,如封装和单一职责原则,并且能够提供更清晰、更灵活的业务逻辑实现。

业务场景

假设我们正在开发一个电子商务平台,其中包含一个核心功能:订单处理。用户可以浏览商品,将商品添加到购物车,下单并支付。

贫血模型(Anaemic Domain Model)示例

在贫血模型中,订单类可能如下所示:

public class Order {
    private int orderId;
    private List<Product> products;
    private double totalAmount;
    
    // 仅提供getter和setter方法
    public int getOrderId() {
        return orderId;
    }

    public void setOrderId(int orderId) {
        this.orderId = orderId;
    }

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }

    public double getTotalAmount() {
        return totalAmount;
    }

    public void setTotalAmount(double totalAmount) {
        this.totalAmount = totalAmount;
    }
    
    // 没有业务逻辑
}

业务逻辑可能在服务层中处理:

public class OrderService {
    public void processOrder(Order order) {
        // 计算总价
        double total = 0;
        for (Product product : order.getProducts()) {
            total += product.getPrice() * product.getQuantity();
        }
        order.setTotalAmount(total);
        
        // 模拟支付逻辑
        boolean isPaymentSuccess = pay(order);
        if (isPaymentSuccess) {
            // 订单处理逻辑...
        } else {
            // 支付失败处理...
        }
    }
    
    private boolean pay(Order order) {
        // 支付逻辑...
        return true;
    }
}

充血模型(Rich Domain Model)示例

在充血模型中,订单类将包含业务逻辑:

public class Order {
    private int orderId;
    private List<Product> products = new ArrayList<>();
    private double totalAmount;
    
    public void addProduct(Product product) {
        products.add(product);
        recalculateTotalAmount();
    }
    
    private void recalculateTotalAmount() {
        totalAmount = products.stream()
                .mapToDouble(product -> product.getPrice() * product.getQuantity())
                .sum();
    }
    
    public boolean pay() {
        // 检查订单总额是否大于0
        if (totalAmount <= 0) {
            return false;
        }
        // 模拟支付逻辑
        return true;
    }
    
    // 实体类包含业务逻辑
    public void processOrder() {
        if (pay()) {
            // 支付成功,进行后续处理
        } else {
            // 支付失败处理...
        }
    }
    
    // Getter和Setter方法...
}

在充血模型中,业务逻辑被封装在实体类中,Order类负责自己的状态管理,如添加商品、重新计算总价和支付处理。服务层或控制器将变得非常薄:

public class OrderService {
    public void createAndProcessOrder(Order order, Product product) {
        order.addProduct(product);
        order.processOrder();
    }
}

对比

  • 业务逻辑位置:在贫血模型中,OrderService类包含业务逻辑;而在充血模型中,Order类自己处理业务逻辑。
  • 职责清晰度:充血模型中,每个实体的职责更加清晰,Order类管理自己的状态和行为。
  • 可维护性:充血模型通常更易于维护,因为业务逻辑集中在领域模型中,而不是分散在多个服务类中。

充血模型提供了一种更符合面向对象原则的设计,使得领域模型更加丰富和自足,同时也便于单元测试和维护。