问题描述
我正在尝试遵循Vaughn Vernon guidelines进行总体设计。
我了解,出于系列中提到的许多原因以及许多DDD从业者的建议,建议将骨料保持尽可能小。
但这会在聚合主机内部处理无关的命令属性时产生问题。
我有一个传入命令,其中包含系统进行完整交易所需的信息。该命令中包含的信息极少,因此无法放入一个汇总中。
因此,我认为这是一项长期的交易。我正在使用saga模式控制整个交易流程。
包装此长期运行事务中涉及的聚合的每个主机都会引发域事件。然后,该领域事件被传奇事件拦截,该事件继而发出后续命令,依此类推,直到业务交易成功执行或完全失败。
在会话Z中从参与者Y发送消息X
例如,此命令将被消息集合的主机拦截。
此主机将重新填充Message聚合,告诉它验证事务的第一部分:
从参与者Y发送消息X 。
MessageCreated ,这里的问题是MessageCreated事件将需要携带其余的业务交易详细信息
对话Z
供后续聚合执行其余的交易。
由于消息聚合不关心 ConversationId ,因此其托管人需要保留该信息(从聚合的角度来看是不必要的)。
当汇总批准交易时,主机将必须创建一个域事件,例如 MessageCreated *,并在其上附加 ConversationId 。
我在这里的关注点是,通过在主机内部保留执行业务交易不必要的信息,随着时间的推移,这些信息会积累起来,充其量会带来更多的业务需求,并且最坏的情况下很可能在主机之间造成逻辑耦合。 / p>
我的问题:
这是处理长期交易的安全方法吗?如果没有,那是更好的选择吗?
解决方法
出于将来的操作目的,需要在操作之间传播无关的数据,这表明重新设计可能是有益的。这是我第一次听说聚合设计,但似乎总的想法是,对聚合的操作不应在聚合上下文中将其任何部分置于无效状态。传奇的基本思想是状态变化是一个事件,事件发生时应广播,而某些事件应触发操作。
一种解决方法是重新考虑消息不依赖于对话的决定。例如:
消息(uuid,内容,时间戳,参与者ID,对话ID)
- 规则:
- 创建活动:MESSAGE_CREATED
- 听事件:
参与者(uuid,名字,姓氏,电子邮件)
- 规则:
- 创建事件:PARTICIPANT_CREATED,EMAIL_CHANGED
- 听事件:
对话(uuid,消息ID列表,参与者ID列表)
- 规则:所有消息都必须属于对话的参与者。
- 创建事件:CONV_CREATED,PERSON_ADD,PERSON_RM,MESSAGE_ADDED
- 听事件:MESSAGE_CREATED
鉴于这些汇总和事件,一种方法是:
- 接收命令
Send Message X from Participant Y in Conversation Z
- 创建消息(uuid,X,Y,Z)-> MESSAGE_CREATED
- 对话接收事件-> MESSAGE_ADDED(如果对话参与者)
从您的问题尚不清楚如何以及为什么以这种方式设计聚合。您提到它们必须最小,但是我认为这不是最重要的方面。聚集者需要实现他们的目标。大小仅在大小太大或无法使用时才重要。例如,一个可能包含数年消息的对话可能不是一个好的设计。但是,可能告诉您的是,一个好的设计不仅是其不断增长的性质,而且它很可能不需要所有消息来实现其目标这一事实(在对话中可能没有涉及所有消息的业务逻辑) )。也许您只需要最后几条消息或其他参与者尚未收到的消息。之后,您可能可以从“对话”中删除消息,并将其留在只读模型中以用于历史/显示目的。
我要说的是,一种解决方案可能是重新设计聚合,这样一来您就不会遇到这个问题。
另一方面,您提到您使用的是Saga模式,但根据说明并不清楚如何进行操作,因为似乎聚合是由事件而不是由saga协调的。
如果您需要使用传奇来协调聚合,则命令将由传奇来处理。然后,您将面临以下两种情况之一:
- 要协调的操作彼此不依赖
- 这些操作相互依赖,并且需要按一定顺序执行(或依赖于先前操作中的数据)
在第一种情况下,传奇仅处理命令并将“较小”命令发送给所有需要协调的集合。这些聚合将执行操作并发布事件或回复命令发送者,这将是一种设计选择。然后,传奇故事会收集所有响应,并且在收到所有响应后,操作便已完成(显然,您需要处理某些操作失败或从未完成的情况)。
在第二种情况下,传奇将处理命令并在内部存储所有必要的信息。然后向第一个聚合发送命令,并等待答复或事件。收到后,它将使用事件中的信息以及命令中接收的信息在过程中发送以下命令,依此类推。
在这两种情况下,传奇都应该能够检测到一个或多个步骤何时不成功,并发送其他命令来补偿或还原成功的步骤。