DDD:Side-Effect-Free功能的难题

时间:2013-01-24 20:37:48

标签: function domain-driven-design

我为这么多问题道歉,但我觉得只有在作为一个单位对待时才最有意义

注意 - 所有引用均来自DDD: Tackling Complexity in the Heart of Software(第250和251页)

1)

  

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

     

...

     

返回结果而不产生副作用的操作是   叫做函数。可以多次调用函数并返回   每次都是相同的值。

     

...

     

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

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

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

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

d)

  

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

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

E)

  

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

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

2)

  

VALUE OBJECTS是不可变的,这意味着,除了   初始化程序仅在创建期间调用,所有操作都是   功能

     

...

     

将逻辑或计算与状态更改混合的操作   应该重构为两个单独的操作。但按照定义,   这种副作用的分离只是简单的命令方法   适用于ENTITIES。完成重构后分开   从查询修改,考虑第二次重构移动   将复杂计算的责任归入VALUE OBJECT。该   通过推导出一个值,通常可以完全消除副作用   OBJECT而不是改变现有状态,或通过移动整个   对VALUE OBJECT负责。

a)

  

VALUE OBJECTS是不可变的,这意味着,除了   初始化程序仅在创建期间调用,所有操作都是   功能...但根据定义,这种副作用的分离   简单命令方法仅适用于ENTITIES。

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

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

c)

  

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

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

d)

  

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

在任何情况下,为什么作者建议我们应该从实体中重构函数并将它们放在VO中?只是因为它使客户端更明显地认为这个操作可能是一个函数?

E)

  

通过推导出值,通常可以完全消除副作用   OBJECT而不是改变现有状态,或通过移动整个   对VALUE OBJECT负责。

这没有意义,因为看起来作者正在争论我们是否将命令(即改变状态的操作)移动到VO中,那么我们本质上将消除任何副作用,即使命令是改变国家。所以任何想法,作者究竟试图说什么?

更新

1b)

  

这取决于观点。数据库查询不会更改状态   因此没有副作用,但它不是确定性的   自然,因为你指出数据可以改变。在书中,   作者指的是与价值对象相关的功能   实体,它们不会自己拨打外部电话。因此,   规则不适用于QandC。

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

1c)

  

QandC本身不会改变状态 - 没有副作用。该   然而,潜在的状态可能会在带外变化。因此,它   不是一个纯粹的功能。

但它并不是意义作者定义的无副作用函数吗?

1d)

  

同样,这是基于CQS。

我知道我重复自己,但我认为书中的讨论是基于CQS而CQS并不认为QandC是无副作用的功能,因为实体可能会返回QandC将来某个时候修改状态(通过其他操作)?

1e)

  

从CQRS的角度来看,它被认为是一个查询,但它不可能   在VO上的纯函数是a的意义上称为函数   由于缺乏决定论而起作用。

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

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

2d)

  

鉴于VO是不可改变的,它们是适合居住的地方   功能。这是从领域知识中解放出来的又一步   技术限制。

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

  • 我假设将功能放入VO的其他原因是因为它是一个更明显(对客户端)这是一个功能?

2e)

  

我认为这是对事件采购的暗示。而不是改变   现有状态,您添加一个表示更改的新事件。那里   仍然是一个净副作用,但现有状态保持稳定。

我必须承认我对偶数源编程一无所知,因为我想首先围绕DDD。无论如何,所以作者并不暗示只是将命令移到VO会自动消除副作用,但是必须采取一些额外的行动(例如实施事件采购),只有他"忘了& #34;提到那个部分?

第二次更新:

2d)的

  

实体的一个明确特征是其身份....   通过将业务逻辑放入VO,您可以将其视为外部的   实体身份的背景。这样可以更轻松地进行测试   其中包括逻辑。

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

2e)

  

是的,这是我对它的理解 - 还有一个网络   效果&#34 ;.但是,有不同的方法可以获得副作用。   一种方法是改变现有状态。另一种方式是建立国家   使用表示该更改的对象进行显式更改。

我 - 只是为了确定...从你的回答中我得知,作者并不意味着仅仅通过将命令移入VO就可以消除副作用?

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

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

第三次更新:

  

将国家管理委派给实体和   对VO的行为实施产生了某些优势。一个是   基本的职责划分。

a)你说的是即使一个方法描述了一个实体的行为(因此包含这个方法的实体遵守SRP)并且因此属于该实体,移动它仍然是一个好主意它进入VO?因此,实质上,我们会将一个实体的责任划分为两个甚至更小的责任?

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

谢谢

1 个答案:

答案 0 :(得分:4)

1a)是的。关于将查询与命令分离的讨论基于Command-query separation principle

1b)这取决于观点。数据库查询不会改变状态,因此没有副作用,但它本质上不具有确定性,因为当您指出数据可以改变时。在本书中,作者指的是与价值对象和实体相关的功能,它们不会自己进行外部调用。因此,规则不适用于QandC。然而,确定性可以被制造出来,提供纯度和纯度。例如,可以创建可序列化的事务,以确保数据在其持续时间内不会发生变化。

1c)QandC本身不会改变状态 - 没有副作用。然而,基础状态可以在带外改变。因此,它不是pure function。但是,QandC不会改变状态的限制仍然很有价值。 CQRS恰当地证明了这个值,这是CQS在分布式场景中的应用。

1d)同样,这是基于CQS。另一个问题是Tell-Don't-Ask principle。然而,鉴于对这些原则的理解,该规则可以弯曲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)是的,这是我对它的理解 - 仍然存在净副作用&#34;。但是,有不同的方法可以获得副作用。一种方法是改变现有状态。另一种方法是使用表示该更改的对象使状态更改显式。

更新2

2d)这个特殊观点可以论证,也可以是偏好问题。一个观点是该想法基于单一责任原则(SRP)。实体的责任是身份与行为和国家的关联。行为将输入与现有状态组合以产生状态转换。将国家管理委派给实体并向VO实施行为会产生某些优势。一个是责任的基本划分。另一个更微妙,也许更有争议。逻辑可以以无状态的方式被考虑。这样可以更容易地思考这种逻辑,更像是在思考所有变化都是明确的数学方程 - 没有隐藏状态。

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

2e.2)是的。

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

更新3

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

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