我为这么多问题道歉,但我觉得它们只有在被视为一个整体时才最有意义
注意 - 所有引述均来自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 中,基本上不会将该实体变成一个纯粹的数据容器(我知道该实体仍将管理其状态,但仍然......)?
谢谢你