DDD - применять инварианты для ассоциаций внутри агрегата - PullRequest
0 голосов
/ 10 марта 2019

Я пытаюсь понять концепции DDD и оступился, когда захотел обновить сущность, которая является частью коллекции, связанной с корневой сущностью. Я пытаюсь применить инвариант от корневого объекта, но обнаружил, что есть способ обойти проверку перед сохранением в базе данных.

Реализована модель предметной области на основе одного из правил DDD:

Корневая сущность имеет глобальную идентификацию и в конечном итоге отвечает за проверку инвариантов .

У меня есть корневая сущность класса Me, и у нее есть коллекция сущностей с именем Friends. Существуют типы друзей, такие как Лучший друг, Просто друг, Рабочий друг

Инвариант: Коллекция друзей может содержать любое количество Just Friend & Work 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;
   }
}

Сценарий: Моя корневая сущность имеет двух друзей в коллекции, один из которых лучший друг, а другой - просто друг. Теперь, если вы попытаетесь изменить тип сущности «просто друг» с «Просто друг» на « Лучший друг », код не должен разрешать и генерировать исключение, поскольку это нарушение бизнес-правила. ,

  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, но ни один из них не показывает реализацию кода.

1 Ответ

1 голос
/ 10 марта 2019

Коллекция друзей может содержать любое количество Just Friend & Work Friend, но должна содержать только одного лучшего друга.

Это говорит о том, что вам может понадобиться такая структура данных, как:

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

То есть, если вы разрабатываете свою модель таким образом, что недопустимые состояния не являются непредставимыми , тогда вы получите некоторую помощь в обнаружении ошибок в логике вашего домена.

В фундаментальномуровень, мы имеем дело с конечным автоматом.Агрегат начинается в некотором начальном состоянии A. Мы вызываем некоторый метод в корне, и либо мы остаемся в том же состоянии, либо переходим в какое-то новое состояние B, которое также удовлетворяет всему инварианту.

Это не должноЯ не чувствую, что мы делаем здесь что-то новое - это просто хорошее «объектно-ориентированное программирование».Любой метод, который мы вызываем для объекта, должен удовлетворять условиям его размещения.Тот факт, что мы решили разделить «совокупность» на несколько объектов, на самом деле не меняет этого.

Использует ли шаблон спецификации / уведомления и проверяет модель домена в доменной служберешает проблему?

Для меня это звучит как куча ненужных осложнений.

...