域模型定义实体之间的关系,我们定义聚合根以提供封装和事务边界。众所周知的关系是一对一关系(实体或值对象包含在聚合根中)、一对多关系(聚合根包含子对象的集合)和多对多关系。后者很困难,因为聚合根之间的多对多关系会使您在事务边界方面遇到麻烦。因此,在许多情况下,多对多关系的一个方向被视为更重要,并且只有该关系被建模为一对多关系。
现在,更进一步。网络。等效合作伙伴之间的多对多关系。如何在不违反聚合根的事务边界的情况下对其进行建模?
看看这个广泛适用的例子:
我有一个带节点的网络。每个节点都有有限数量的端口。一个端口只能连接到另一个节点上的一个端口。我必须能够使用端口添加和删除节点之间的连接。
一种直观的方法是将节点建模为包含端口的聚合根。连接似乎是价值对象,一个端口可以有一个连接。我可以实现一个 Node.ConnectTo(nodeId, portId) 方法,该方法将连接(节点 A 上的端口 X 和节点 B 上的端口 Y 之间)添加到聚合根节点 A。最好我会调用此方法两次,一次节点 A 和节点 B 上一次并将其包装在事务中。但是,这会违反事务边界,因此我决定仅将其存储在节点 A 上。
要在应用程序客户端上查看节点 B 上的连接,需要一个单独的读取模型。但这没问题,CQRS 架构为我们提供了这些可能性。因此,添加、删除和查看连接不是问题。
当我想在将连接添加到端口之前验证端口是否仍然空闲时,就会出现问题。尊重我们的事务边界的结果是(在写入模型中)聚合根可能不知道端口已经连接的事实,但可能存储在任何其他聚合根中。
当然,您可以信任客户端的验证,继续添加连接,如果您要添加它的节点没问题,并依靠运行一致性检查的进程来执行无效连接的补偿操作。但与围绕两个 ConnectTo 调用包装事务相比,这对我来说似乎很重要......
这让我认为也许我的聚合根被错误地选择了. 我开始将节点和网络视为聚合根,其中网络是连接的集合。网络聚合的好处是您始终可以验证添加或删除连接。除非一个新的连接会导致两个现有网络的加入......并且您的聚合可能会变得很大,可能只会导致一个巨大的网络。也不可行。
那么,您认为这应该如何建模呢?您是否看到将聚合根视为事务边界的解决方案,您可以验证您的网络,并且您不会冒险将整个网络存储为单个聚合?还是我在这里要求所有 3 个 CAP,这根本不可能?
问问题
231 次
2 回答
0
好的,我阅读并对此进行了更多思考,我想这是“正确”的做法:
- 在节点 A 上执行 ConnectTo 方法之前,您使用最终一致的视图模型作为数据源验证节点 B 上的端口是否仍然空闲(而不是无法有效验证这一点的域模型,见上文)。
- ConnectTo 仅在节点 A 上运行,因此不会违反事务边界。
- 如果视图模型无法连接节点 B 上的端口,因为它已经在使用中,则发生了真正的并发异常,必须发出信号。需要采取一些措施(手动干预或自动化流程必须采取措施)。这种并发异常的概率通常非常低。
于 2011-11-26T12:55:02.237 回答
0
我认为你的“新方式”是有缺陷的,因为视图模型不应该产生一个“以某种方式”传播回域模型的异常。领域模型需要自己解决这个问题。
因此,在这种情况下(一对一绑定),您可以利用域模型中的事件,这样
NodeA.connect("port1").to(NodeB).on("port3");
NodeA 自己保留“port1”。
NodeA 向 NodeB 发送“portConnectionRequest”。
如果可用,NodeB 会绑定“port3”。
NodeB 发送“portConnectionConfirmed”或“portConnectionDenied”。
NodeA 接收到事件并采取相应的行动。
以上假设可靠的消息传递,这在 JVM 中很容易实现,但在分布式环境中要困难得多,但这正是您更想要的地方。如果无法提供可靠的消息传递系统,我认为您将遇到拜占庭协议问题问题,或者它的一个子集。
于 2012-06-01T02:37:52.120 回答