DDD инварианты бизнес-правила и валидация
Я ищу советы о том, где добавить правила проверки для доменных сущностей, а также рекомендации по реализации. Я искал и не нашел того, что искал, или я упустил это.
Я хотел бы знать, каков рекомендуемый способ проверки того, что свойства не являются нулевыми, в определенном диапазоне или длине и т. д... Я видел несколько способов использования IsValid() и других дискуссий о принудительном применении в конструкторе, чтобы сущность никогда не находилась в недопустимом состоянии или не использовала препроцессинг и постпроцессинг, а также другие, использующие FluentValidation api, как инварианты влияют на DRY и SRP.
Может ли кто-нибудь привести мне хороший пример того, где следует размещать такого рода проверки, когда используется служба приложений, ограниченный контекст, доменная служба, агрегатный корень, наслоение сущностей. Куда это ведет и каков наилучший подход?
Спасибо.
2 ответа:
При моделировании доменной сущности лучше всего учитывать реальные следствия. Предположим, вы имеете дело с сущностью
Employee
.Сотрудникам нужно имя
Мы знаем, что в реальном мире у сотрудника всегда должно быть имя. Невозможно, чтобы у сотрудника не было имени. Другими словами, нельзя "сконструировать" сотрудника, не указав его имени. Итак, используйте параметризованные конструкторы! Мы также знаем, что имя сотрудника не может измениться - поэтому мы предотвратите это даже путем создания частного сеттера. Использование системы типов .NET для проверки Вашего сотрудника-это очень сильная форма проверки.public string Name { get; private set; } public Employee(string name) { Name = name; }
Допустимые имена имеют некоторые правила
Теперь это начинает становиться интересным. У имени есть определенные правила. Давайте просто возьмем упрощенный маршрут и предположим, что допустимое имя-это имя, которое не является нулевым или пустым. В приведенном выше примере кода следующее бизнес-правило не проверяется. На этом этапе мы можем до сих пор в настоящее время создают недействительных сотрудников! Давайте предотвратим это когда-либо, изменив наш сеттер:
Лично я предпочитаю иметь эту логику в частном сеттере, чем в конструкторе. Сеттер не совсем невидим. Сама сущность все еще может изменить его, и нам нужно обеспечить его действительность. Кроме того, всегда бросайте исключения!public string Name { get { return name; } private set { if (String.IsNullOrWhiteSpace(value)) { throw new ArgumentOutOfRangeException("value", "Employee name cannot be an empty value"); } name = value; } }
А как насчет раскрытия некоторой формы метода
IsValid()
?Возьмем вышеприведенную сущность
Employee
. Где и какIsValid()
метод работы?Разрешите ли вы создать недействительного сотрудника, а затем ожидать, что разработчик проверит его валидность с помощью проверки
IsValid()
? Это слабый дизайн - прежде чем вы узнаете об этом, безымянные сотрудники будут курсировать вокруг вашей системы, вызывая хаос.Но, возможно, вы хотели бы раскрыть логику проверки имени?
Мы не хотим перехватывать исключения для потока управления. Исключение составляют случаи катастрофических системных сбоев. Мы тоже не хочу дублировать эти правила проверки в нашей кодовой базе. Так что, возможно, разоблачение этой логики проверки не такая уж плохая идея (но все же не самая лучшая!).
Что вы могли бы сделать, так это предоставить статический метод
IsValidName(string)
:public static bool IsValidName(string name) { return (String.IsNullOrWhiteSpace(value)) }
Наше свойство теперь несколько изменится:
public string Name { get { return name; } private set { if (!Employee.IsValidName(value)) { throw new ArgumentOutOfRangeException("value", "Employee name cannot be an empty value"); } name = value; } }
Но в этом дизайне есть что-то подозрительное...
Теперь мы начинаем создавать методы проверки отдельных свойств нашей сущности. Если свойство имеет все возможно, это признак того, что мы можем создать для него ценностный объект!
public PersonName : IEquatable<PersonName> { public string Name { get { return name; } private set { if (!PersonName.IsValid(value)) { throw new ArgumentOutOfRangeException("value", "Person name cannot be an empty value"); } name = value; } } private PersonName(string name) { Name = name; } public static PersonName From(string name) { return new PersonName(name); } public static bool IsValid(string name) { return !String.IsNullOrWhiteSpace(value); } // Don't forget to override .Equals }
Теперь наша сущность
Employee
может быть упрощена (я исключил проверку нулевой ссылки):public Employee { public PersonName Name { get; private set; } public Employee(PersonName name) { Name = name; } }
Наш клиентский код теперь может выглядеть примерно так:
if(PersonName.IsValid(name)) { employee = new Employee(PersonName.From(name)); } else { // Send a validation message to the user or something }
Так что же мы здесь сделали?
Мы позаботились о том, чтобы наша модель предметной области всегда была последовательной. Исключительно важный. Недопустимая сущность не может быть создана. Кроме того, у нас есть используемые объекты ценности обеспечивают дальнейшее "богатство".
PersonName
дал клиентскому коду больше контроля и больше мощности, а также упростилEmployee
.