C# 如果业务逻辑规则应该在持久层实现,那么如何在域模型中表示该规则?

C# 如果业务逻辑规则应该在持久层实现,那么如何在域模型中表示该规则?,c#,domain-driven-design,C#,Domain Driven Design,我目前正在学习DDD,似乎我误解了其中的一些核心思想。 假设有一个“A”集合。它引用了其他几个实体和值对象。以编程方式创建或更改它是不可能的(不是故意的),因此它将变得无效。“A”可以通过构造函数创建。可以更改现有聚合 公共A类:智能化{ 公共int Id{get;} 公共字符串名称{get;} 公共B{get;} 公共C{get;} 公共A(字符串名,B) { 名称=名称; B=B; //这里提供一些验证逻辑,以确保Name和B属性是正确的。 //如果不满足业务规则,则抛出异常。 } 公共无效

我目前正在学习DDD,似乎我误解了其中的一些核心思想。 假设有一个“A”集合。它引用了其他几个实体和值对象。以编程方式创建或更改它是不可能的(不是故意的),因此它将变得无效。“A”可以通过构造函数创建。可以更改现有聚合

公共A类:智能化{
公共int Id{get;}
公共字符串名称{get;}
公共B{get;}
公共C{get;}
公共A(字符串名,B)
{
名称=名称;
B=B;
//这里提供一些验证逻辑,以确保Name和B属性是正确的。
//如果不满足业务规则,则抛出异常。
}
公共无效编辑(字符串newName,B newB)
{
//这里提供一些验证逻辑,以确保具有新值的聚合是正确的。
Name=newName;
B=新的;
}
}
还有一个“C”门。它由“A”引用。如果没有一个“A”(一对一关系),则无法创建它。它还包含一些“D”实体(一对多)

公共D类:安全性
{
公共int Id{get;}
}
公共C类:公共性
{
私有只读列表_ds=new List();
公共int Id{get;}
公共IReadOnlyCollection Ds=>\U Ds;
公共无效地址(D)
{
//这里添加一些验证逻辑。
_ds.加入(d);
}
}
所以现在我想介绍一个新的业务规则:只有当“a”没有“C”或者它的“C”没有任何“D”时,我们才能编辑它。朴素的实现非常简单:

公共A类:智能化
{
//只需添加此新方法:
public bool CanEdit()=>C==null | |!C.Ds.Any();
//在Edit()中调用上述方法,如果为false则抛出。
}
然而,这种方法以后肯定会“咬”我。在“让我们看看”a“s”用例中,我不需要在应用程序中加载任何“C”或“D”。但是,我需要知道是否可以编辑这些“A”实体。因此,该规则的实际实现应该出现在持久层中


如何在域模型代码中表达这样的规则,并在持久层中实现它?我应该将其放在存储库或域服务中吗?也许使用CQRS样式的查询更好?或者,对每个人来说,都有一个解释应该在持久性层中实现什么规则的注释更容易吗

我认为这是一个你一开始就不应该遇到的问题。您的聚合不应直接引用其他聚合。当您听说一个聚合可以引用另一个聚合时,这只意味着它可以存储另一个聚合的Id,而不是整个聚合。因此,您不应该让聚合A具有类型为B和C的属性。相反,您应该拥有ID为聚合B和C的属性

通过从聚合中删除此问题,您就删除了主要问题,但您得到了一个新问题:如何实际检查聚合a中依赖于聚合C的业务规则

如果不讨论真实域的细节,这是不可能回答的。但是考虑以下两个选项:

  • 你错了。当您拥有跨多个聚合的业务规则时,这是您应该考虑的第一件事。首先,为什么会发生这种情况?可能它们应该是单个聚合,或者可能C的一部分属于a。聚合的目的是封装业务规则和需要评估的数据。如果聚合没有它需要的数据,那么它的设计就有问题

  • 您的聚合是正确的,但A需要了解C的一些情况。这种情况经常发生。A负责折扣,C负责用户忠诚度。如果用户积分超过1000分而成为高级用户,我们希望给予10%的折扣。按照您的方法,A可以参考C并查看用户点。但是,让C封装让用户成为高级用户的逻辑是更好的设计。因此,C将跟踪用户点。在某个时刻,它将决定用户已成为高级用户,并将引发UserBecamePremiumEvent。聚合A将接收该事件并在其数据中存储IsPremium。在下次购买时,A将执行“如果用户是IsPremium,则应用10%折扣”