Я пытаюсь понять концепции 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;
}
}
Сценарий: Моя корневая сущность имеет двух друзей в коллекции, один из которых лучший друг, а другой - просто друг. Теперь, если вы попытаетесь изменить тип сущности «просто друг» с «Просто друг» на « Лучший друг », код не должен разрешать и генерировать исключение, поскольку это нарушение бизнес-правила. ,
Обычная реализация для принудительного применения инварианта при обновлении друга.
public void DomainService_UpdateFriend()
{
var me = repo.GetMe("1");
me.UpdateFriend(2,"john doe","Best Friend"); //Throws Exception
repo.SaveChanges();
}
Реализация обхода бизнес-правил
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();
}
Это приводит меня к вопросам:
- Неужели дизайн на моделях неправильный?
Если да, то как это неправильно и какова должна быть правильная реализация?
Если нет, то не является ли это нарушением вышеупомянутого правила DDD?
Будет ли правильная реализация следующего
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("");
}
}
Место применения инварианта / проверки зависит от добавления объекта в коллекцию. против обновление объекта в коллекции? То есть
проверка правильности Addfriend (), но не во время UpdateFriend ().
Напоминает ли это нам еще одно правило в DDD, которое гласит
Когда совершается изменение любого объекта в пределах Агрегата, все инварианты всего Агрегата должны быть выполнены.
Как выглядит эта реализация?
Решает ли проблема использование шаблона спецификации / уведомления и проверка модели домена в доменной службе?
Использование шаблона спецификации будет более подходящим, когда существует несколько контекстов или несколько состояний модели. Но что, если нет многократного контекста и состояний?
Хотя я ценю ответы всех видов, я ищу реализацию кода, чтобы сделать это правильно. Я видел много вопросов по SO, но ни один из них не показывает реализацию кода.