0

我试图理解 DDD 概念,当我想更新作为与根实体关联的集合的一部分的实体时偶然发现。我试图从根实体强制执行不变量,但我发现有一种方法可以在将验证保存到数据库之前绕过验证。

基于 DDD 规则之一实现了域模型:

根实体具有全局身份,并最终负责检查不变量

我有一个 Me 类根实体,它有一个名为 Friends 的实体集合。有最好的朋友,只是朋友,工作朋友等朋友类型

不变: friends 集合可以包含任意数量的 Just Friend 和 Work Friend,但只能包含一个 Best friend

public class Me : Entity, IAggregateRoot
{
    public string Name {get; }

    private List<Friend> friends;
    public IReadonlyCollection<Friend> Friends => friends;

    public Me(string name)
    {
      Name = name;
      friends = new List<Friend>();
    }

    public void AddFriend(string name, string type)
    {
      //Enforcing invariant
      if(CheckIfBestFriendRuleIsSatisfied(type))
      {
         Friend friend = new Friend(name, type);
         friends.Add(friend);
      }
      else
         throw new Exception("There is already a friend of type best friend.");
    }

    public void UpdateFriend(int id, string name, string type)
    {
      //Enforcing invariant
      if(CheckIfBestFriendRuleIsSatisfied(id, type))
      {
         Friend friend = firends.First(x => x.Id == id);
         friend.SetType(type);
         friend.SetName(name);
      }
      else
         throw new Exception("Cannot update friend.");
    }
}

public class Friend : Entity
{
   public string Name {get; }
   public string Type {get; }

   public Friend(string name, string type)
   {
     Name = name;
     Type = type;
   }

   public void SetType(string type)
   {
       Type = type;
   }

   public void SetName(string name)
   {
       Name = name;
   }
}

场景:我的根实体在集合中有两个朋友,一个是最好朋友类型,另一个是朋友类型。现在,如果您尝试将“justfriend”实体的类型从“Just Friend”更改为“ Best Friend ”,代码不应允许并抛出异常,因为这是违反业务规则的。

  1. 在更新朋友时强制执行不变的正常实现。

     public void DomainService_UpdateFriend()
     {
        var me = repo.GetMe("1");
    
        me.UpdateFriend(2,"john doe","Best Friend"); //Throws Exception
    
        repo.SaveChanges();
     }
    
  2. 业务规则绕过实现

     public void DomainService_UpdateFriend()
     {
        var me = repo.GetMe("1");
    
        var friend = me.Friends.First(x => x.Id == 2);
        friend.SetType("Best Friend"); // Business rule bypassed
        friend.SetName("John Doe");
    
        repo.SaveChanges();
     }
    

这让我想到了问题:

  1. 模型上的设计是错误的吗?
    如果是,它是怎么错的,正确的实现应该是什么?
    如果不是,那么这是否违反了上述 DDD 规则?
  2. 以下将是正确的实施

        public class Me : Entity, IAggregateRoot
        {
            ...Properties
            ...ctor
            ...AddFriend
    
             public void UpdateFriend(int id, string name, string type)
             {
                  Friend friend = firends.First(x => x.Id == id);
                  if(friend != null)
                  {
                      friend.SetNameAndType(name,type, this);//Passing Me root entity
                  }
                  else
                     throw new Exception("Could not find friend");
              }
          }
    
        public class Friend : Entity
        {
            ...Properties
            ...ctor
    
           //passing root entity as parameter or set it thru ctor
           public void SetupNameAndType(string name, string type, Me me)
           {
               if(me.CheckIfBestFriendRuleIsSatisfied(id, type))
               {
                  Name = name;
                  Type = type;
               }
               else
                 throw new Exception("");
           }
        }
    
  3. 强制不变/验证的位置从将实体添加到集合更新集合中的实体不同?这是在 Addfriend() 正确时验证,但在 UpdateFriend() 时不正确。

这是否提醒我们 DDD 中的另一条规则,即 当对聚合边界内的任何对象的更改被提交时,必须满足整个聚合的所有不变量。

  • 这个实现看起来如何?

  • 使用规范/通知模式并验证域服务上的域模型是否解决了这个问题?

  • 当有多个上下文或模型的多个状态时,使用规范模式会更合适。但是如果没有多个上下文和状态呢?

    虽然我很欣赏各种答案,但我正在寻找代码实现以使其正确。我见过很多关于 SO 的问题,但没有一个显示代码实现。

4

1 回答 1

3

朋友集合可以包含任意数量的 Just Friend 和 Work Friend,但只能包含一个 Bestfriend。

这表明您可能需要如下数据结构:

class Me {
    List<Friend> friends;
    Friend bestFriend;
    // ...
}

也就是说,如果您将模型设计为无法表示无效状态,那么您将在捕获域逻辑中的错误方面获得一些帮助。

从根本上讲,我们正在处理状态机。聚合从某个初始状态 A 开始。我们在根上调用某个方法,或者我们保持在相同的状态,或者我们转换到也满足整个不变量的某个新状态 B。

它不应该让人觉得我们在这里做了任何新的事情——这只是很好的“面向对象编程”。我们在对象上调用的任何方法都应该满足其后置条件。我们选择将“聚合”划分为多个对象的组合这一事实并没有改变这一点。

使用规范/通知模式并验证域服务上的域模型是否解决了这个问题?

在我看来,这听起来像是一堆不必要的并发症。

于 2019-03-10T15:33:22.093 回答