获取域实体的附加数据
我有一个域聚合,将其称为“订单”,其中包含订单行列表。 订单跟踪订单行上的金额总和。 客户有一个运行的“信用”余额,他们可以从中订购,该余额是通过汇总数据库交易历史来计算的。 一旦他们用完“池”中的所有资金,他们就无法再订购任何产品。
因此,每次向订单中添加一行时,我都需要检查池中还剩下多少,以及订单是否将其推过。 池中的金额不断变化,因为其他相关客户不断使用它。
问题是,从 DDD 角度思考,我如何获得这个数量,因为我不想用 DataContext 问题污染我的域层(此处使用 L2S)。 由于我不能只从域查询数据库,因此如何获取该数据以便验证业务规则?
这是使用领域事件的实例吗?
I have a domain Aggregate, call it "Order" that contains a List of OrderLines. The Order keeps track of the sum of the Amount on the Order Lines. The customer has a running "credit" balance that they can order from that is calculated by summing the history of their database transactions. Once they use up all the money in the "pool" they can't order any more products.
So every time a line is added to the order, I need to get to check how much is left in the pool and if the order pushes them over it. The amount in the pool is continually changing because other related customers are continually using it.
The question is, thinking in terms of DDD, how do I get that amount since I don't want to pollute my Domain Layer with DataContext concerns (using L2S here). Since I can't just query out to the database from the domain, how would I get that data so I can validate the business rule?
Is this an instance where Domain Events are used?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您的订单聚合应该被完全封装。 因此,它需要能够确定添加项目是否有效,即是否超出客户信用。 有多种方法可以做到这一点,但它们都依赖于订单存储库返回知道如何执行此特定操作的特定聚合。 例如,这可能是与您用于满足订单的订单聚合不同的订单聚合。
您必须认识到,然后在代码中捕获您希望订单在本例中履行特定角色的事实,即添加其他行项目的角色。 您可以通过为此角色创建一个接口以及为该角色提供内部支持的相应聚合来实现此目的。
然后,您的服务层可以向您的订单存储库请求满足此显式角色接口的订单,并且存储库因此拥有足够的信息来了解您需要什么来构建可以满足该要求的东西。
例如:
现在,您的服务代码将类似于:
接下来,实现 IAddItemsToOrder 的 Order 类需要一个客户实体,以便它可以检查信用余额。 因此,您只需通过定义特定接口来级联相同的技术即可。 订单存储库可以调用客户存储库以返回履行该角色的客户实体并将其添加到订单聚合中。
因此,您将拥有一个基本的
ICustomer
接口,然后以从其派生的ICustomerCreditBalance
接口的形式获得显式角色。ICustomerCreditBalance
既充当客户存储库的标记接口,告诉它您需要客户做什么,因此它可以创建适当的客户实体,并且它具有方法和/或属性来支持特定角色。 类似于:显式角色接口为存储库提供了他们所需的关键信息,以做出正确决定从数据库中获取哪些数据,以及是急切地还是延迟地获取数据。
请注意,在本例中,我已将
CreditBalance
属性放在ICustomerCreditBalance
接口上。 但是,它也可以位于基本ICustomer
接口上,然后ICustomerCreditBalance
变成一个空的“标记”接口,让存储库知道您要查询贷方余额。 这一切都是为了让存储库知道您想要它返回的实体扮演什么角色。正如您在问题中提到的,将所有这些结合在一起的最后一部分是域事件。 如果超出客户的信用余额,订单可以引发故障域事件,以通知服务层订单无效。 另一方面,如果客户有足够的信用,它可以更新客户对象上的余额或引发域事件以通知系统的其余部分需要减少余额。
我还没有将域事件代码添加到
CartService
类中,因为这个答案已经相当长了! 如果您想了解更多有关如何做到这一点的信息,我建议您发布另一个针对该特定问题的问题,我将在那里进行扩展;-)Your Order aggregate should be fully encapsulated. It therefore needs to be able to determine whether it's valid to add an item, i.e. whether or not the customer credit is exceeded. There are various ways to do this but they all depend on the Order repository returning a specific aggregate that knows how to do this particular thing. This will probably be a different Order aggregate from one you'd use for satisfying orders, for example.
You have to recognise, then capture in code, the fact that you're expecting the order to fulfil a particular role in this case, i.e. the role of adding additional line items. You do this by creating an interface for this role and a corresponding aggregate that has the internal support for the role.
Then, your service layer can ask your Order repository for an order that satisfies this explicit role interface and the repository thus has enough information about what you need to be able to build something that can satisfy that requirement.
For example:
Now, your service code would look something like:
Next, your Order class that implements
IAddItemsToOrder
needs a customer entity so that it can check the credit balance. So you just cascade the same technique by defining a specific interface. The order repository can call on the customer repository to return a customer entity that fulfils that role and add it to the order aggregate.Thus you'd have a base
ICustomer
interface and then an explicit role in the form of anICustomerCreditBalance
interface that descends from it. TheICustomerCreditBalance
acts both as a marker interface to your Customer repository to tell it what you need the customer for, so it can create the appropriate customer entity, and it has the methods and/or properties on it to support the specific role. Something like:Explicit role interfaces give repositories the key information they need to make the right decision about what data to fetch from the database, and whether to fetch it eagerly or lazily.
Note that I've put the
CreditBalance
property on theICustomerCreditBalance
interface in this case. However, it could just as well be on the baseICustomer
interface andICustomerCreditBalance
then becomes an empty "marker" interface to let the repository know that you're going to be querying the credit balance. It's all about letting the repository know just what role you want for the entity it returns.The final part which brings this all together, as you mentioned in your question, is domain events. The order can raise a failure domain event if the customer's credit balance would be exceeded, to notify the service layer that the order is invalid. If the customer has enough credit, on the other hand, it can either update the balance on the customer object or raise a domain event to notify the rest of the system that the balance needs to be reduced.
I've not added the domain event code to the
CartService
class since this answer is already rather long! If you want to know more about how to do that, I suggest you post another question targeting that specific issue and I'll expand on it there ;-)在这种情况下,我使用事件或委托来减轻责任。 也许向您展示的最简单的方法是使用一些代码。
您的 Order 类将有一个
Predicate
,用于确定客户的信用额度是否足以处理订单行。您可能会有一个 CustomerService 类或类似的类来提取可用的信用额度。 您可以在添加任何订单行之前设置 CanAddOrderLine 谓词。 每次添加行时都会检查客户的信用。
毫无疑问,您的实际情况会比这更复杂。 您可能还想查看
Func
委托。 委托或事件可用于在下达订单行后减少信用金额,或者在客户超出订单中的信用限额时触发某些功能。祝你好运!
In such a scenario, I off-load responsibility using events or delegates. Maybe the easiest way to show you is with some code.
Your Order class will have a
Predicate<T>
that is used to determine if the customer's credit line is big enough to handle the order line.You will probably have a CustomerService class or something like that to pull the available credit line. You set the CanAddOrderLine predicate before adding any order lines. This will perform a check of the customer's credit each time a line is added.
No doubt your real scenario will be more complicated than this. You may also want to check out the
Func<T>
delegate. A delegate or event could be useful for decrementing the credit amount after the order line is placed or firing some functionality if the customer goes over their credit limit in the order.Good luck!
除了获取“池”值(我将使用 OrderRepository 上的方法查询该值)的问题之外,您是否考虑过此问题的锁定影响?
如果“池”不断变化,那么其他人的事务是否有可能在您的规则通过之后、但在您将更改提交到数据库之前悄悄进入?
Eric Evans 在他的书(“聚合”)的第六章中提到了这个问题。
In addition to the problem of getting the "pool" value (where I would query the value using a method on an OrderRepository), have you considered the locking implications for this problem?
If the "pool" is constantly changing, is there a chance that someone elses transaction creeps in just after your rule passes, but just before you commit your changes to the db?
Eric Evans refers to this very problem in Chapter 6 of his book ("Aggregates").