如何在代码中对域实体建模?
How to model a Domain Entity in code?
我熟悉 DDD 和实体的概念。
根据 DDD,实体是一个从根本上由其身份定义的对象。
比如说,在我的项目中,我已经确定 Account
是一个实体。所以在代码中,这将由带有标识符字段的 class 表示,类似于
class Account {
private Id id
private AccountStatus status
...
}
因为这是一个实体,所以它的所有字段都可以在其生命周期内发生变化,除了 Id
。
我的问题是,在状态变化和对象平等方面用代码对此建模的最佳方法是什么。
- 状态变化
由于实体的状态会随着时间的推移而变化,class 应该建模为可变的 class 还是应该在每次状态变化时创建新的引用而不可变?
- 平等
既然 Account
仅根据其 Id
来识别,那么 equals 方法是否应该只比较对象的标识符?考虑所有 或 没有字段的潜在问题是什么。
通常,对于给定的情况,实体和值对象是互斥的概念。不同的域对不同的概念也有不同的关注,一个这样的概念可能是给定域中的值对象和另一个给定域中的实体。
一个这样的例子是金钱作为一个概念。对于电子商务环境,Money 可能是一个 ValueObject,因为该域只关心它的价值而不关心它的持久身份(对于这样的系统,10 美元的 2 个实例将是相同的,你不会关心将它们分开命名) .
因为联邦银行(或任何印刷货币的实体)以完全不同的方式对待货币。这个领域实际上用序列号标记了每张票据,并且非常关心每一张票据的历史(它是什么时候打印的,是什么型号,可能还有它在哪里打印)。因此,该域可能会将货币建模为实体。
这些差异也会影响平等的处理方式。
对于 ValueObject,相等性通常定义为“具有相同值的 2 个实例”,因此,您最好匹配每个值字段以建立相等性。 Money 实例“10 USD”等于另一个 Money 实例“10 USD”,但与 Money 实例“10 EUR”不同(它们的 FaceValue 属性 相同,但它们的 Currency 不同,因此您可以' 互换使用它们)。
现在,对于实体,您关心给定实例在其整个生命周期中的身份(作为概念,而不是实例化)。因此,如果您有一个序列号为 1234 且属性为“10 / USD / NearMint”的 Money 实例并且它损坏了,它会更改并保留其序列号 1234,但其属性更新为“10 / USD / Stained”。它仍然是相同的账单,并且对于所有帐户,它应该将相等比较器响应为 true。
为了完成这个冗长的回答,"setter" 部分也依赖于域。通常,ValueObjects 不应该改变它们的内部状态。但是实体应该只根据领域规则改变它们的内部状态。在金钱的例子中,即使它是一个实体,也可能完全没有任何意义去猜测给定钞票的面值或货币。然而,它的状态 NearMint -> Stained -> Damaged -> 等可能会随着时间的推移而演变。此外,您可能应该考虑不要总是使用直接设置器,而是在您的实体中创建域有意义的方法,以便在处理状态转换时更好地表达无处不在的语言。
我建议您复习一下 Clojure 的纪元时间模型。 Stuart Halloway 2010 年的演讲 Perception and Action details the epochal time model.
从某种意义上说,实体是对不可变状态的可变引用。
在面向对象的风格中,我们可能会做类似的事情
Entity {
MutableRef<State> mutableRef;
void change (...) {
State current = mutableRef.get()
State next = someFunction(current, ...)
mutableRef.set(next)
}
}
(我们通常不这样做,因为在 OO 的 Java 谱系中,常见的做法是操纵可变值)。
My question is, what is the best way to model this in code? I see two approaches,
Since this is an entity, conceptually, all its fields can change so the model should support mutation(setters) and equality defined only on Id field.
Model this as a Value Object, i.e immutable, final fields, equality defined over all fields and on mutation, create and return a new object.
如果用您的业务语言将帐户描述为随时间变化的事物,您可能希望使用实体模型。
这并不一定意味着“二传手”;更常见的风格是修改器应该用业务语言编写。我们告诉实体该做什么
account.close()
了解该操作如何影响底层数据结构是实体的工作。
关于身份,这是 Evans 最近的评论:
I have come to believe that an entity shouldn't even have an equality operation
该视频剪辑似乎取自 2014 年 Pluralsight 课程 Domain-Driven Design Fundamentals 的模块,作者是 Julie Lerman 和 Steve Smith。
我熟悉 DDD 和实体的概念。
根据 DDD,实体是一个从根本上由其身份定义的对象。
比如说,在我的项目中,我已经确定 Account
是一个实体。所以在代码中,这将由带有标识符字段的 class 表示,类似于
class Account {
private Id id
private AccountStatus status
...
}
因为这是一个实体,所以它的所有字段都可以在其生命周期内发生变化,除了 Id
。
我的问题是,在状态变化和对象平等方面用代码对此建模的最佳方法是什么。
- 状态变化
由于实体的状态会随着时间的推移而变化,class 应该建模为可变的 class 还是应该在每次状态变化时创建新的引用而不可变?
- 平等
既然 Account
仅根据其 Id
来识别,那么 equals 方法是否应该只比较对象的标识符?考虑所有 或 没有字段的潜在问题是什么。
通常,对于给定的情况,实体和值对象是互斥的概念。不同的域对不同的概念也有不同的关注,一个这样的概念可能是给定域中的值对象和另一个给定域中的实体。
一个这样的例子是金钱作为一个概念。对于电子商务环境,Money 可能是一个 ValueObject,因为该域只关心它的价值而不关心它的持久身份(对于这样的系统,10 美元的 2 个实例将是相同的,你不会关心将它们分开命名) .
因为联邦银行(或任何印刷货币的实体)以完全不同的方式对待货币。这个领域实际上用序列号标记了每张票据,并且非常关心每一张票据的历史(它是什么时候打印的,是什么型号,可能还有它在哪里打印)。因此,该域可能会将货币建模为实体。
这些差异也会影响平等的处理方式。
对于 ValueObject,相等性通常定义为“具有相同值的 2 个实例”,因此,您最好匹配每个值字段以建立相等性。 Money 实例“10 USD”等于另一个 Money 实例“10 USD”,但与 Money 实例“10 EUR”不同(它们的 FaceValue 属性 相同,但它们的 Currency 不同,因此您可以' 互换使用它们)。
现在,对于实体,您关心给定实例在其整个生命周期中的身份(作为概念,而不是实例化)。因此,如果您有一个序列号为 1234 且属性为“10 / USD / NearMint”的 Money 实例并且它损坏了,它会更改并保留其序列号 1234,但其属性更新为“10 / USD / Stained”。它仍然是相同的账单,并且对于所有帐户,它应该将相等比较器响应为 true。
为了完成这个冗长的回答,"setter" 部分也依赖于域。通常,ValueObjects 不应该改变它们的内部状态。但是实体应该只根据领域规则改变它们的内部状态。在金钱的例子中,即使它是一个实体,也可能完全没有任何意义去猜测给定钞票的面值或货币。然而,它的状态 NearMint -> Stained -> Damaged -> 等可能会随着时间的推移而演变。此外,您可能应该考虑不要总是使用直接设置器,而是在您的实体中创建域有意义的方法,以便在处理状态转换时更好地表达无处不在的语言。
我建议您复习一下 Clojure 的纪元时间模型。 Stuart Halloway 2010 年的演讲 Perception and Action details the epochal time model.
从某种意义上说,实体是对不可变状态的可变引用。
在面向对象的风格中,我们可能会做类似的事情
Entity {
MutableRef<State> mutableRef;
void change (...) {
State current = mutableRef.get()
State next = someFunction(current, ...)
mutableRef.set(next)
}
}
(我们通常不这样做,因为在 OO 的 Java 谱系中,常见的做法是操纵可变值)。
My question is, what is the best way to model this in code? I see two approaches,
Since this is an entity, conceptually, all its fields can change so the model should support mutation(setters) and equality defined only on Id field.
Model this as a Value Object, i.e immutable, final fields, equality defined over all fields and on mutation, create and return a new object.
如果用您的业务语言将帐户描述为随时间变化的事物,您可能希望使用实体模型。
这并不一定意味着“二传手”;更常见的风格是修改器应该用业务语言编写。我们告诉实体该做什么
account.close()
了解该操作如何影响底层数据结构是实体的工作。
关于身份,这是 Evans 最近的评论:
I have come to believe that an entity shouldn't even have an equality operation
该视频剪辑似乎取自 2014 年 Pluralsight 课程 Domain-Driven Design Fundamentals 的模块,作者是 Julie Lerman 和 Steve Smith。