目录

  • 目录是空的
首页 文章 2021 06 08

领域模型中的 Entity, Value Object 和 Service

更新时间:

作者: Alex

Entity (Reference Object)

Entity 对象不通过属性定义,而是通过连续性和标识定义。Entity 具有生命周期,这期间它们的形式和属性可能发生根本改变,但必须保持一种内在的连续性, (5 岁的儿童与 20 岁的成人可以是同一个人,这并不由包括名字在内的自身属性决定,而是具有一个外部的唯一标识); Entity 的职责、属性和关联必须由其标识来决定,而不依赖于其所具有的属性。总的来说,Entity 满足以下两个条件:

  • Entity 在整个生命周期中具有连续性。
  • Entity 之间的区别不由属性决定。

Entity 标识和编程语言层面的标识

面向对象编程语言为每个对象自动创建了标识(内存地址或引用),所以在本地运行环境中,每个对象都可以看作一个 Entity,但这种标识机制在其他应用领域中却没有意义。 所以 Entity 标识应该是一种具有意义的特殊属性,不应该把它交给语言的自动特性来处理,而且并不是所有对象都属于 Entity。

以银行为例,同一天,同一账户的两笔数额相同的存款,这两笔交易具有各自的标识(支票号码),所以每笔交易都是一个 Entity;另外,交易的金额属性可能是某种货币对象的实例, 但它们没有标识, 因为从业务上不需要区分它们,因此金额不属于 Entity.

Entity 对象建模

Entity 对象的基本职责是确保连续性,所以在建模时应该只添加那些用于识别、查找或匹配对象的属性和行为,并将其他属性和行为转移到与 Entity 关联的其他对象中。 Entity 往往通过协调其关联对象的操作来完成自己的职责。

Value Object

Value Object 是用于描述领域的某个方面而本身没有概念标识的对象。对于此类对象,我们只关心它们是什么,而不关心它们是谁; Value Object 模型元素应该能够表示出其属性的意义,并为它提供相关功能;Value Object 应该是简单的,无标识符的。

区别对待 Entity 和 Value Object

一个系统中跟踪 Entity 标识很重要,但盲目的为对象添加唯一标识会增加系统复杂性和分析工作量,所以要根据业务区别对待系统中的模型, 仅在真正需要时才建立 Entity.

Value Object 的不变性

一般来说,Value Object 应该是不可改变的,这样可以确保共享和引用传递的安全性。如果属性值发生变化,则应该使用不同的 Value Object 进行替换, 而不是修改现有的 Value Object.

但有时出于性能考虑,也需要让 Value Object 成为可变的,主要考量以下因素:

  • value 频繁改变
  • 创建或删除 Value Object 开销很大
  • value 的共享不会提高系统性能

总结为,如果一个 value 的实现是可变的,那么就不能共享它。原则是无论是否共享 Value Object,都应该尽可能的将它们设计成不可变的。

Denormalization (非规范化)

一个不可变的 Value Object 可以安全的被其他系统引用,但在某些场景下,引用可能会造成额外的 I/O 操作,例如数据库底层对处在不同页面的数据对象的引用; 在分布式系统中,对另一台机器上的 Value Object 的引用,也可能导致消息响应缓慢。诸如此类的问题,解决方案是在系统中的不同位置保存多个相同的 Value Object 副本,这种技术称为 Denormalization. 当访问时间比存储空间或维护的简单性更重要时,通常使用这种技术。

Value Object 之间没有双向关联

由于没有标识符,Value Object 之间的双向关联是没有意义的,因为我们不能说一个 Value Object 指向另一个 Value Object,只能说两个 Value Object 是等同的。因此在设计时,应该完全清除 Value Object 之间的双向关联。

Service

有一些特殊的活动或动作,它们不属于任何领域对象的事务,这些操作常常会牵扯到很多领域对象,需要协调这些对象以便使它们工作,而这会产生对所有这些对象的依赖, 因此这些操作无法简单的放到某个 Entity 或 Value Object 中,这些操作通常作为 Service 的接口出现。

Service 强调的是对象之间的关系,它只定义了“能够为客户做什么”,Service 一般根据某个活动来命名(是动词而不是名词),Service 也可以在领域层中使用,它们的职责以及履行职责的接口应该作为模型的一部分加以定义。

Service 在模型中是独立存在的,不像 Entity 或 Value Object 那样具有封装的状态,而且 Service 不应该代替 Entity 和 Value Object 的行为, Service 具有以下特点:

  • 其与领域相关的操作不属于 Entity 或 Value Object 的自然职责
  • 接口名称应来自于模型中的术语。
  • 操作应该是无状态的。

区分不同层级的 Service

以上讨论的是领域层 Service,我们也需要区分处于不同层面的 Service.

Service 一般划分为基础设施层领域层应用层。一般情况下,基础设施层封装了底层的通用操作(纯技术 Service),应用层用于设置底层 Service, 而领域层则负责确认是否满足临界值。

例如,银行为两个账户提供转账服务,并在转账后向用户发短信通知,在此业务中:

  • 应用层发送消息给领域层,要求其执行转账操作,接收到领域层的确认之后,再决定使用基础设施层的 Service 发送短信。
  • 领域层与相关的 Account (账户)Ledger (总账) 对象进行交互,执行相应的借入和贷出操作,并向应用层返回结果(允许或拒绝转账)。
  • 根据应用层的指示发送短信、Email 或其他形式的通知。

领域层服务的意义

领域对象的行为是细粒度的,如果没有领域层 Service,直接由应用层操作对象的话,可能会把领域层的知识泄漏到应用层,这导致应用层不得不处理复杂的、 细致的交互,从而使得领域知识蔓延到应用层或用户界面代码中,而领域层也会丢失这些知识。所以引入领域层 Service 有助于在应用层和领域层之间保持一条明确的界限。

前一篇