Home
img of docs

了解领域对象如何帮助建模和实现业务逻辑,提高软件设计的质量和可维护性。

chou403

/ DDD

/ c:

/ u:

/ 8 min read


一学一个不吱声

在领域驱动设计(Domain-Driven Design, DDD)中,领域对象是领域模型中的核心概念,它代表系统在业务逻辑中的实体或数据,并且封装了与业务相关的行为。领域对象不仅仅是数据结构,它应该具备清晰的业务语义,负责处理业务规则和状态变化,避免业务逻辑分散在服务层或应用层中。

为了更好地理解领域对象,让我们从几个关键方面深入探讨:

1. 领域模型的基本概念

  • 实体(Entity): 实体是指在领域中具有唯一标识的对象,通常是系统中的重要业务对象。它不仅仅有数据属性,还包括行为和业务规则。

    示例: 在电商系统中,Order(订单)就是一个实体。它有一个唯一的标识符(订单ID),订单的状态(已创建,已发货,已取消等),以及业务逻辑,比如修改订单状态,取消订单等。

  • 值对象(Value Object): 值对象没有唯一标识,通常表示某些属性的组合。它们是不可变的,主要用于封装属性,而不是行为。

    示例: 在一个订单中,Address(地址)可以是一个值对象,它表示订单的收货地址,包括省市,街道,邮编等信息。地址的变化不会影响订单的唯一标识。

  • 聚合(Aggregate): 聚合是一个领域对象的集合,它们被当作一个单元一起修改。聚合有一个根实体(称为聚合根),只有通过这个聚合根才能访问聚合中的其他对象。

    示例: 在订单系统中,Order 可能是一个聚合根,而 OrderItem(订单项)是聚合的一部分。外部代码只能通过 Order 来操作订单项,确保业务一致性。

  • 工厂(Factory): 工厂模式用于创建复杂的领域对象或聚合。通常领域对象的构造比较复杂,因此使用工厂方法来封装构造过程。

  • 仓储(Repository): 仓储用于管理领域对象的持久化。它提供了从数据库中查找和存储实体的方式,但隐藏了数据访问的具体实现细节。


2. 充血模型 vs 贫血模型

领域对象的关键在于它不仅仅是数据的载体,它还应包含与业务逻辑相关的行为。这就是所谓的充血模型(Rich Domain Model)与贫血模型(Anemic Domain Model)的区别:

  • 充血模型: 在充血模型中,领域对象不仅包含属性,还包含与这些属性相关的业务逻辑。对象是行为与状态的结合体,业务逻辑封装在领域对象中。这符合 DDD 的思想,强调通过领域对象来表达业务行为。

    示例:

       class Order {
    	private status: string;
    
    	constructor() {
    		this.status = "Created";
    	}
    
    	cancelOrder() {
    		if (this.status !== "Shipped") {
    			this.status = "Cancelled";
    		} else {
    			throw new Error("Order cannot be cancelled once shipped");
    		}
    	}
    }

    在上面的代码中,Order 对象不仅存储了订单状态,还包含了取消订单的业务逻辑。

  • 贫血模型: 在贫血模型中,领域对象只是数据的容器,业务逻辑全部放在服务层。这种方式可能违背了 DDD 的核心思想,因为业务逻辑脱离了数据,导致代码的组织不清晰。

    示例:

       class Order {
    	status: string;
    	// 仅包含数据,没有行为
    }
    
    class OrderService {
    	cancelOrder(order: Order) {
    		if (order.status !== "Shipped") {
    			order.status = "Cancelled";
    		} else {
    			throw new Error("Order cannot be cancelled once shipped");
    		}
    	}
    }

    在贫血模型中,Order 仅包含数据,所有的业务逻辑都在 OrderService 中,这种设计可能导致业务逻辑散乱,不利于维护。


3. 领域对象的职责

领域对象的核心职责是封装业务规则和状态管理,因此它不仅仅是数据库表的映射,而是业务的忠实表达。通过领域对象来处理业务逻辑,可以确保业务规则的一致性和完整性。

领域对象的设计要点:

  1. 行为优于数据: 领域对象不仅持有数据,更应该包含与数据相关的行为,避免将业务逻辑放到应用层或服务层。

  2. 领域对象的边界: 每个领域对象应该专注于它的职责,不要跨越领域边界。例如,订单的状态管理应该由 Order 处理,而库存的管理应该由 Inventory 处理。

  3. 保持不变性: 值对象(Value Objects)在领域中通常是不可变的,它们的修改需要创建新的对象,这样可以确保状态的一致性和正确性。


4. 领域对象示例

以下是一个电商系统中的 Order 类,它不仅封装了订单的属性,还包含了核心的业务逻辑,如添加商品,修改订单状态等。

   class Order {
	private id: string;
	private status: string;
	private items: OrderItem[];

	constructor(id: string) {
		this.id = id;
		this.status = "Created";
		this.items = [];
	}

	addItem(item: OrderItem) {
		if (this.status !== "Created") {
			throw new Error("Cannot add items to a non-created order");
		}
		this.items.push(item);
	}

	shipOrder() {
		if (this.items.length === 0) {
			throw new Error("Cannot ship an order with no items");
		}
		this.status = "Shipped";
	}

	cancelOrder() {
		if (this.status === "Shipped") {
			throw new Error("Shipped orders cannot be cancelled");
		}
		this.status = "Cancelled";
	}
}

class OrderItem {
	constructor(
		public productId: string,
		public quantity: number,
	) {}
}

在这个例子中,Order 封装了订单的状态及其行为(添加商品,发货,取消订单等),而这些逻辑是订单业务的核心。在 DDD 的设计中,领域对象负责管理和保护这些核心业务逻辑。


5. 总结

领域对象是 DDD 中的核心概念,它不仅仅是数据结构,更重要的是它应该封装与业务相关的逻辑。通过领域对象,可以确保业务规则的一致性,可维护性,并通过对象的行为来表达业务语义。

  • 充血模型是 DDD 强调的设计方式,通过领域对象封装业务逻辑。
  • 贫血模型虽然简单,但将业务逻辑放到了服务层,导致业务代码分散,容易出现问题。

DDD 的核心是将业务逻辑和数据紧密结合在领域对象中,使得系统的设计更加贴合业务。