7

我为这么多问题道歉,但我觉得它们只有在被视为一个整体时才最有意义

注意 - 所有引述均来自DDD:解决软件核心的复杂性(第 250 和 251 页)

1)

操作大致可以分为两类,命令和查询。

...

返回结果而不产生副作用的操作称为函数。一个函数可以被多次调用,每次都返回相同的值。

...

显然,您无法避免大多数软件系统中的命令,但可以通过两种方式缓解该问题。首先,您可以将命令和查询严格隔离在不同的操作中。确保导致更改的方法不返回域数据并保持尽可能简单。以不会导致可观察到的副作用的方法执行所有查询和计算

a) 作者暗示查询是一个函数,因为它不会产生副作用。他还指出,函数将始终返回相同的值,我假设他的意思是对于相同的输入,我们将始终得到相同的输出?

b)假设我们有一个QandC(int entityId)查询特定域实体的方法,它从中提取某些值,这些值又用于初始化一个新的值对象,然后这个 VO 返回给调用者。根据上面的引用不是QandC一个函数,因为它不会改变任何状态吗?

c) 但作者还认为,对于相同的输入,一个函数总是会产生相同的输出,而 的情况并非如此QandC,因为如果我们多次调用QandC,它将产生不同的结果,假设在两次调用之间的时间实体被修改甚至删除。因此,我们怎么能声称QandC是一个函数?

d)

确保导致更改的方法不返回域数据...

原因是返回的非 VO 的状态可能会在未来的某些操作中发生变化,因此这些方法的副作用是不可预测的?

e)

确保导致更改的方法不返回域数据...

返回实体查询方法是否仍被视为函数,即使它没有改变任何状态?

2)

VALUE OBJECTS 是不可变的,这意味着除了仅在创建期间调用的初始化程序之外,它们的所有操作都是函数。

...

将逻辑或计算与状态更改混合的操作应重构为两个单独的操作。但根据定义,这种将副作用分离为简单命令方法的方法仅适用于实体。在完成重构以将修改与查询分开后,考虑进行第二次重构,将复杂计算的责任转移到 VALUE OBJECT 中。通过派生 VALUE OBJECT 而不是更改现有状态,或者将整个责任转移到 VALUE OBJECT 中,通常可以完全消除副作用。

一个)

VALUE OBJECTS 是不可变的,这意味着除了仅在创建期间调用的初始化程序之外,它们的所有操作都是函数……但是根据定义,这种将副作用分离为简单命令方法的方法仅适用于实体。

我认为作者是说在 VO 上定义的所有方法都是函数,这是没有意义的,因为即使在 VO 上定义的方法不能改变自己的状态,它仍然可以改变其他非 VO 对象的状态?!

b) 假设在实体上定义的方法不会改变任何状态,我们是否认为这样的方法是一个函数,即使它是在实体上定义的?

C)

...考虑进行第二次重构,将复杂计算的责任转移到 VALUE OBJECT 中。

为什么作者建议我们只应该从实体重构那些执行复杂计算的函数?为什么我们不应该重构更简单的函数呢?

d)

...考虑进行第二次重构,将复杂计算的责任转移到 VALUE OBJECT 中。

无论如何,为什么作者建议我们应该从实体中重构函数并将它们放在 VO 中?仅仅因为它让客户更清楚这个操作可能是一个函数?

e)

通过派生 VALUE OBJECT 而不是更改现有状态,或者将整个责任转移到 VALUE OBJECT 中,通常可以完全消除副作用。

这没有意义,因为作者似乎在争论如果我们将命令(即改变状态的操作)移动到 VO 中,那么我们将在本质上消除任何副作用,即使命令正在改变状态。所以有什么想法,作者实际上想说什么?

更新:

1b)

这取决于视角。数据库查询不会更改状态,因此没有副作用,但是它本质上不是确定性的,因为正如您指出的那样,数据可以更改。在本书中,作者指的是与值对象和实体相关联的函数,它们本身并不进行外部调用。因此,这些规则不适用于 QandC。

所以作者只描述了不进行外部调用的函数,因此这不是QandC作者描述的一种函数吗?

1c)

QandC 本身不会改变状态 - 没有副作用。但是,基础状态可能会在带外更改。因此,它不是一个纯函数。

但这也不是作者定义它们的意义上的无副作用功能吗?

1d)

同样,这是基于 CQS。

我知道我在重复自己,但我认为书中的讨论是基于 CQS 的,并且 CQS 不认为是无副作用的函数,因为有时会通过修改其状态(通过其他操作)QandC返回实体QandC未来?

1e)

从 CQRS 的角度来看,它被认为是一个查询,但由于缺乏确定性,它不能被称为函数,因为 VO 上的纯函数是函数。

  • 我不太明白你想说什么(令人困惑的部分以粗体显示)。也许虽然QandC被认为是一个查询,但由于返回一个实体,它不被认为是一个函数,并且这样的副作用是不可预测的,这使得QandC本质上是不确定的

  • 所以作者只是在隐含假设下做出这些陈述(参见1e中的引用 ),即 VO 中定义的任何操作都不会尝试改变非 VO 对象的状态?

2d)

鉴于 VO 是不可变的,它们是存放纯函数的合适场所。这是将领域知识从技术限制中解放出来的又一步。

  • 我不明白为什么将功能从实体转移到 VO 将有助于将领域知识从技术限制中解放出来(我也不确定你所说的技术是什么意思——技术相关的技术或......)?

  • 我认为将函数放入 VO 的其他原因是因为(对于客户端)这是一个函数更加明显?

2e)

我认为这是对事件溯源的暗示。您无需更改现有状态,而是添加一个表示更改的新事件。仍然存在净副作用,但现有状态保持稳定。

我必须承认我对偶数源编程一无所知,因为我想首先围绕 DDD 进行研究。无论如何,所以作者并没有暗示仅仅将命令移动到 VO 会自动消除副作用,而是必须采取一些额外的行动(例如实现事件源),只是他“忘记”提到那部分?

第二次更新:

2d)

实体的定义特征之一是它的身份……通过将业务逻辑放入 VO,您可以在实体身份的上下文之外考虑它。这使得更容易测试这个逻辑等等。

我有点理解你的意思(当从远处思考这个概念时),但另一方面我真的不明白。为什么实体内的功能会受到该实体的身份的影响(假设该功能是纯功能,换句话说,它不会改变状态并且是确定性的)?

2e)

是的,这就是我对它的理解——仍然存在净“副作用”。但是,有不同的方法可以达到副作用。一种方法是改变现有状态。另一种方法是使用表示该更改的对象使状态更改显式。

我-只是为了确定...从您的回答中,我了解到作者并没有暗示仅通过将命令移入VO就可以消除副作用吗?

II - 好的,如果我理解正确,我们可以将命令移动到 VO 中(即使 VO 不应该改变任何东西的状态,因此不应该引起任何副作用)并且 VO 中的这个命令仍然被允许产生某种副作用,但是通过使状态更改显式(我将其解释为更改的内容作为 VO 返回给调用者),这种副作用在某种程度上更容易接受(或更多可控)?

3)我必须说我还是不太明白为什么状态改变方法 SC 不应该返回域对象。也许是因为非 VO 可能会在未来的某些操作中发生变化,因此 SC 的副作用非常不可预测?

第三次更新:

将状态管理委托给实体并将行为的实施委托给 VO 会产生一定的优势。一是基本职责分工。

a)您的意思是,即使方法描述了实体的行为(因此包含此方法的实体遵守 SRP )并且因此属于实体,将其移动到 VO 中可能仍然是个好主意?因此,本质上,我们会将一个实体的职责划分为两个更小的职责?

b)但是不会将行为移动到 VO 中,基本上不会将该实体变成一个纯粹的数据容器(我知道该实体仍将管理其状态,但仍然......)?

谢谢你

4

1 回答 1

5

1a) 是的。关于从命令中分离查询的论述是基于命令-查询分离原则

1b) 这取决于视角。数据库查询不会更改状态,因此没有副作用,但是它本质上不是确定性的,因为正如您指出的那样,数据可以更改。在本书中,作者指的是与值对象和实体相关联的函数,它们本身并不进行外部调用。因此,这些规则不适用于QandC. 然而,决定论可以被捏造,提供一定程度的“纯粹性”。例如,可以创建一个可序列化的事务,它可以确保数据在其持续时间内不会发生变化。

1c)QandC本身不会改变状态 - 没有副作用。但是,基础状态可能会在带外更改。因此,它不是纯函数。但是,QandC不改变状态的限制仍然很有价值。CQRS是CQS在分布式场景中的应用,恰如其分地体现了价值。

1d) 同样,这是基于 CQS。对此的另一种看法是“告诉-不问”原则。然而,鉴于对这些原则的理解,该规则可以弯曲 IMO。例如,一个副作用方法可以返回一个表示结果的 VO。但是,在某些情况下,例如 CQRS + 事件溯源,可能希望命令返回 void。

1e) 从 CQRS 的角度来看,它被认为是一个查询,但由于缺乏确定性,它不能被称为函数,因为 VO 上的纯函数是函数。

2a) 不,VO 函数不应该改变任何东西的状态,它应该返回一个新对象。

2b) 是的。

2c)因为功能纯度在更复杂的场景中往往变得更加重要。但是,正如您所指出的,这并不是一个明确而明确的规则。它不应该基于复杂性,而是基于手头的域。

2d) 鉴于 VO 是不可变的,它们是存放纯函数的合适场所。这是将领域知识从技术限制中解放出来的又一步。

2e) 我认为这是对事件溯源的暗示。您无需更改现有状态,而是添加一个表示更改的新事件。仍然存在净副作用,但现有状态保持稳定。

更新

1b) 是的。

1c) 它是一个无副作用的函数,但它不是一个确定性函数,因为它不能被认为在给定相同输入的情况下总是返回相同的值。例如,返回当前时间的函数是无副作用的函数,但在后续调用中肯定不会返回相同的值。

1d)QandC可以被认为是无副作用的,但不是纯粹的。另一种看待函数纯度的方法是参照透明性——在不改变程序行为的情况下用函数调用的值替换函数调用的能力。换句话说,提出问题并不会改变答案。QandC可以保证,但只能在事务等上下文中。所以 QandC 可以被认为是一个函数,但仅限于特定的上下文。

1e) 我认为令人困惑的部分是作者专门讨论了 VO 和实体上的功能 - 而不是数据库查询,因为我们正在谈论两者。我的陈述将讨论扩展到数据库查询和给定某些限制的 CQRS,即环境事务。

2d)我可以看到我说的有点含糊,我变得懒惰了。实体的定义特征之一是其身份。它在其整个生命周期中保持其身份,而其状态可能会发生变化。通过将业务逻辑放入 VO,您可以在实体身份的上下文之外考虑它。这使得更容易测试这个逻辑等等。

2e) 是的,这就是我对它的理解——仍然存在净“副作用”。但是,有不同的方法可以达到副作用。一种方法是改变现有状态。另一种方法是使用表示该更改的对象使状态更改显式。

更新 2

2d)这一点可以争论,也可以是一个偏好问题。一种观点是该想法基于单一职责原则(SRP)。实体的责任是将身份与行为和状态联系起来。行为将输入与现有状态相结合以产生状态转换。将状态管理委托给实体并将行为的实施委托给 VO 会产生一定的优势。一是基本职责分工。另一个更微妙,也许更有争议。逻辑可以以无状态的方式考虑。这使得思考这样的逻辑更容易,更像是思考一个所有变化都是显式的数学方程——没有隐藏状态。

2e.1) 是的,消除净副作用会改变行为,这不是目标。

2e.2) 是的。

3) 返回 void 的命令有几个优点。一是他们自然而然地更擅长异步场景——无需等待结果。另一个是它允许您将操作表示为单个命令对象 - 再次,因为没有返回值。这适用于 CQRS 和事件溯源。在这些情况下,任何命令输出都作为事件而不是结果进行调度。但同样,如果这些要求不适用,则返回结果对象可能是合适的。

更新 3

a) 是的,这是一种特定类型的分区。

b) 实体的职责是通过委托给 VO 并应用由此产生的状态变化来协调行为。

于 2013-01-24T21:25:51.220 回答