如何处理(部分)依赖的聚合根? [英] How to handle (partially) dependant aggregate roots?

查看:62
本文介绍了如何处理(部分)依赖的聚合根?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有 Product 的域概念。



Product 有一些 GeneralDetails ,可以说: sku,名称,描述
同时,产品具有一些 ProductCalculations 部分,会计师可以在其中输入不同的值,例如 purchasePrice,stockLevelExpenses,wholeSalesPrice,retailPrice



到目前为止,到目前为止,产品看起来像这样:

 类产品{
GeneralDetails Details;
ProductCalculations计算;

ChangeDetails(常规详细信息){}
重新计算(ProductCalculations计算{}

}

此设置将使产品成为总根,但现在,我想以产品经理可以的方式对其进行拆分输入/更新产品详细信息,但该会计师可以介入并针对给定产品进行无间断更改,而不会出现并发问题
建议将其分为2个单独的汇总根。



但是,然后,删除 ProductDetails 聚合必须意味着也删除 ProductCalculations ,并且它应该以事务方式进行。



假定它们是2个聚合根,这意味着它们有2个单独的存储库,并带有相应的 Delete 方法,如何将其实现为原子事务?



我唯一能想到的就是删除 ProductDetails 并具有处理程序时引发事件( DomainService ),它使用一些特殊的存储库来处理多个聚合根上的事务。



这种方法是否存在问题? /还是有更好的方法来处理它?<​​/ p>

PS。
删除 ProductDetails 时,我不允许最终的一致性。



PS2。
根据@Jon的评论,详细信息计算创建和删除应该以以下方式进行同步:创建/删除详细信息时,还应该创建/删除计算
另一方面,它们的更新应该完全独立。

解决方案

我几乎在所有方面都同意@plalx 。但是,我想花点时间讨论一下。



我发现通常在单笔交易中创建两个或多个相关聚合的成本很少(在单个有界上下文中)。毕竟,如果这些聚合不存在,那么就不会有并发冲突,就没有争用,也没有太大的区别。此外,您不需要处理部分创建的状态(认为状态在聚合之间是分开的)。可以使用最终的一致性来做到这一点,并且在某些情况下这是一种更好的方法,但是在大多数情况下并没有很大的好处。甚至弗农(Vernon)在他的《实现域驱动的设计》(Implementing Domain-Driven Design)一书中也将这种用例称为违反规则的有效理由。如果您同时删除并汇总另一个用户正在更新,该怎么办?当您尝试在同一事务中修改/删除更多聚合时,发生这种冲突的可能性就会增加。这些聚合之间始终存在上游/下游关系吗?我的意思是,如果用户删除 A 并且必须删除 B ,请更新<$ c的用户$ c> B 没有权力或声音来取消删除,因为她正在向汇总状态提供更多信息?



这是一个非常棘手的问题,在大多数情况下,这是您需要与领域专家讨论的问题,并且在很少的实际情况下,如果答案是最终的一致性,您将无法承受。我发现,在许多情况下,最好放一个标志以将聚合标记为非活动,并通知在一段时间后将其删除。如果没有用户具有足够的权限请求,该用户的聚集再次再次变为活动状态,则将其删除。这有助于用户在误删除某些聚合时不会自杀。



您已经提到过,您不希望用户花大量时间修改一个聚合。是一个删除,但是这对交易的贡献不大。但是,这在整个架构中非常依赖。该用户可以将聚合加载到自己的内存空间中,然后删除。不管您是在交易内部删除还是用户都在浪费时间。更好的解决方案可能是发布一个域事件,该事件会触发某种推送通知给用户,因此她知道发生了删除,并且可以停止工作(或者,如果您采用这种方法,则可以取消该删除)。



对于报告和计算,在很多情况下,这些脚本可以跳过兄弟姐妹聚集消失的记录,因此用户不会注意到缺少的部分或



如果出于某种原因仍需要删除同一事务中的多个聚合,则只需在应用程序服务中启动事务并使用存储库即可



因此,总结一下:




  • 创建聚合时,每笔交易修改一个聚合的规则并不重要。

  • 删除许多聚合非常有效(大部分时间),最终一致性y,并且经常一次禁用一次这些汇总比立即执行删除要好。

  • 通过适当的通知比使用事务更好地防止用户浪费时间。

  • 如果确实需要在单个事务中执行这些操作,则应明确地在应用程序中管理该事务。使用域服务执行所有必需的操作(大多数事务是应用程序所关注的事务除外),使该逻辑回到域层。


I have domain concept of Product.

Product have some GeneralDetails, lets say: sku, name, description. At the same time, Product have some ProductCalculations part where accountants can put different values like purchasePrice, stockLevelExpenses, wholeSalesPrice, retailPrice.

So, so far, Product would look something like:

class Product{
   GeneralDetails Details;
   ProductCalculations Calculations;

   ChangeDetails(GeneralDetails details){}
   Recalculate(ProductCalculations calculations{}

}

This setup would make Product an aggregate root. But now, i want to split it in a way that Product manager can input/update product details but then that accountant can step in and intependently change calculations for given product without concurrency issues. That would suggest splitting it into 2 separate aggregate roots.

But then, deleting ProductDetails aggregate must mean deleting ProductCalculations too and it should happen in transactional way.

Assuming they are 2 aggregate roots, meaning they have 2 separate repositories with corresponding Delete methods, how to implement this as an atomic transaction?

The only thing i can think about is to raise event when ProductDetails gets deleted, have a handler (DomainService) that uses some special repository that handles transactions over multiple aggregate roots.

Is there some problem with that approach and/or is there some better way to handle it?

PS. I cannot allow eventual consistency when ProductDetails is deleted.

PS2. Based on comments from @Jon, Details and Calculations create&delete should be synced in a way that when Details are created/deleted, Calculations should also be created/deleted. On the other hand, their updates should be completely independent.

解决方案

I agree with @plalx in almost every point. However I want to do my bit to the discussion.

I've found that there is usually a very little cost in creating two or more related aggregates inside a single transaction (inside a single bounded context). After all, if those aggregates don't exist yet there cannot be a concurrency conflict, there is no contention and no much difference. Furher, you don't need to deal with partially created state (thinking that state is split between aggregates). It is possible to do that using eventual consistency, and there are situations where that is a better approach, but most of the time there is no great benefit. Even Vernon in his book Implementing Domain-Driven Design mentions this use case as "valid reason to break the rules".

Deleting more than one aggregate is a different story. What should happen if you delete and aggregate that another user is updating at the same time? The probability of such a conflict increases as more aggregates you try to modify/delete in the same transaction. Is there always an upstream/downstream relationship between those aggregates? I mean, if an user deletes A and B must be also deleted, have the user that is updating B no "power" or "voice" to cancel that deletion since she is providing more information to the state of the aggregate?

Those are a very tricky questions and most of the time it is something you need to discuss with a domain expert, and there are very few real scenarios when the answer is something you can't afford with eventual consistency. I discovered that in many cases is preferable to put a "flag" marking the aggregate as "inactive", notifying that will be deleted after some period of time. If no user with enough permission request that aggregate to become active again, then it gets deleted. That helped users to not kill themselves when they delete some aggregate by mistake.

You've mentioned that you don't want a user to spend hours modifying one aggregate if there is a deletion, but that is something that a transaction doesn't contribute much. This is very dependent in the whole architecture, though. That user could have loaded the aggregate into her own memory space and then a deletion occurs. It doesn't matter if you delete inside a transaction, the user is still wasting time. A better solution could be to publish a domain event that triggers some sort of push notification to the user, so she knows that a deletion happened and can stop working (or request a cancellation of that deletion, if you follow such approach).

For the reports and calculations, there are many cases when those "scripts" can skip records where the sibling aggregate is gone, so users doesn't notice there is a missing part or there is no complete consistency yet.

If for some reason you still need to delete several aggregates in the same transaction you just start a transaction in an application service and use repositories to perform the deletion, analogous to the creation case.

So, to summarize:

  • The rule of "modify one aggregate per transaction" is not that important when there is a creation of aggregates.
  • Deletion of many aggregates works quite well (most of the time) with eventual consistency, and very often just disabling those aggregates, one at a time, is better than performing the deletion immediately.
  • Preventing an user from wasting time is better achieved with proper notifications than transactions.
  • If there is a real need to perform those actions inside a single transaction, then manage that transaction in the application an be explicit. Using a domain service to perform all the required operations (except for the transaction that is mostly an application concern) brings that logic back to the domain layer.

这篇关于如何处理(部分)依赖的聚合根?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆