我的 DDD 逻辑属于哪里?
Eric Evans 的书说服了我,并将 DDD 集成到我的框架中。所有基本元素(服务、存储库、有界上下文等)都已实现,现在我正在寻找有关如何正确集成它的反馈。
我有一些业务逻辑,在创建或修改实体时必须执行。这个例子是一个非常简单的例子。大多数业务逻辑将变得更加复杂。
该业务逻辑可以分为以下操作:
- 更新计算字段;
- 更新聚合根内的子记录。创建聚合根时,这需要创建默认子记录。更新聚合根时,如果聚合根上的特定字段发生更改,则需要删除现有子记录并创建新记录;
- 将聚合根的开始和结束日期传播到聚合根内子记录的开始和结束日期。在某些情况下,这些必须保持同步;
- 将聚合根的字段传播到不同的聚合根。
我的第一次尝试是将所有这些放在聚合根上,但我觉得这是行不通的。我在集成此逻辑时遇到以下问题:
- 所有这些操作都必须作为一个整体完成,并且不应作为单独的操作提供。这导致测试非常困难(TDD);
- 我不清楚这些操作是否可以转移到服务中。这样做的原因是它们在聚合根之外没有任何意义,但这会使 TDD 变得更容易;
- 某些逻辑会根据是创建新实体还是修改现有实体而发生变化。我应该将这两个分支放在更新逻辑中,还是应该创建两个完全不同的路径来共享不区分基于创建/修改的业务代码。
对于上述问题的任何帮助以及一般的其他反馈将不胜感激。
I’ve been persuaded by Eric Evans’ book and am integrating DDD into my framework. All basic elements (services, repositories, bounded contexts, etc) have been implemented and now I’m looking for feedback on how to correctly integrate this.
I have some business logic which has to be performed when an entity is created or modified. This example is a very simple one. Most business logic will become much more complex.
This business logic can be split up into the following actions:
- Update calculated fields;
- Update a child record inside the aggregate root. When creating the aggregate root this entails creating a default child record. When updating the aggregate root this entails removing the existing child record and creating a new one if a specific field on the aggregate root has changed;
- Propagate start and end date of the aggregate root to the start and end date of the child records inside the aggregate root. These must be kept in sync under certain circumstances;
- Propagate a field of the aggregate root to a different aggregate root.
My first attempt is to put all of this on the aggregate root, but I feel this is not going to work. I have the following problems integrating this logic:
- All these actions have to be completed as a single whole and should not be made available as separate actions. This has the result that this is going to be very difficult to test (TDD);
- I am not clear on whether any of these actions can be moved out to a service. The reason for this is that they make no sense outside of the aggregate root, but it would make TDD a lot easier;
- Some logic changes depending on whether a new entity is created or an existing one is modified. Should I put these two branches inside the update logic or should I make two entirely different paths that share the business code that does not differentiate based create/modify.
Any help on the above issues would be greatly appreciated and other feedback in general.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您描述的算法应该保留在聚合根中,否则您最终会得到贫血域模型,除了将字段传播到另一个聚合根之外,我将在其中描述我认为您稍后应该做什么。
就 TDD 而言,在聚合根上具有“包”访问权限的方法(例如“calculate()”)应该协调整个操作,服务或存储库对象通常会调用该操作。这就是测试应该执行的操作与设置实例变量的不同组合相结合,聚合根应公开其实例变量、子集合,并且每个子集合应通过 getters 公开其实例变量 - 这允许测试在所有情况下验证其状态(如果您需要隐藏)。信息使这些 getter 打包或私有访问,并使用您的单元测试框架将它们公开以进行测试。
对于您的测试环境,请考虑 模拟存储库对象(您正在使用依赖注入对吧?)返回如果没有硬编码值,请考虑使用诸如 dbunit 之类的东西来处理已知状态的数据库。
就逻辑更改而言,创建与修改,您指的是如何持久化还是有实际的算法需要考虑?如果是前者,我会让存储库负责,如果是后者,我会创建两个单独的方法(例如“calculateCreate()”和“calculateUpdate()”),其中calculate()将酌情委托。
此外,还需要考虑一个并发问题,因为听起来计算值依赖于可变字段。因此,要么需要仔细锁定,要么需要聚合根,一次只能由客户端使用一次。这也适用于跨聚合传播字段 - 我可能会使用存储库来实现此目的 - 但您需要仔细考虑这应该或不应该如何影响使用存储库对象的其他客户端。
The algorithm you've described should remain in the aggregate root, elsewise you end up with an anemic domain model, excepting propagating a field to another aggregate root where I will describe what I think you should do later.
As far as TDD is concerned, a method with "package" access on the aggregate root (e.g. "calculate()", should coordinate the entire action, which either the service or repository object would normally call. This is what tests should exercise in conjunction with setting different combinations of instance variables. The aggregate root should expose its instance variables, the children collection, and each child should expose its instance variables, through getters - this allows tests to validate their state. In all cases if you need to hide information make these getters package or private access and use your unit testing framework to make them public for the purpose of testing.
For your testing environment consider mocking the repository objects (you're using dependency injection right?) to return hard coded values. Short of this consider using something like dbunit to work with a database in a known state.
As far as logic changes are concerned create vs. modify, are you referring to how to persist or is there an actual algorithm to consider? If the former, I would make the repository responsible, if the latter I would make two separate methods (e.g. "calculateCreate()" & "calculateUpdate()") which calculate() would delegate as appropriate.
Also, there's a concurrency issue to think about as well because it sounds as if calculated values rely on mutable fields. So either need to have careful locking or aggregate roots that can only be used by a client once at a time. This also applies to propagating a field across aggregates - I would probably use the repository for this purpose - but you need to think carefully on how this should or should not impact other clients who are using the repository object.