Проверка в доменном дизайне


53

Как вы относитесь к проверке сложных агрегатов в проекте, управляемом доменом? Вы объединяете свои бизнес-правила/логику проверки?

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

Но как насчет комплексной проверки, которая включает в себя несколько моделей? Где вы обычно размещаете эти правила & методов в своей архитектуре? И какие шаблоны, если они используются, используются для их реализации?

36

мне нравится решение Джимми Богард к этой проблеме. У него есть запись в блоге под названием "Entity validation with visitors and extension methods", в которой он представляет очень элегантный подход к проверке сущности, предлагающий реализацию отдельного класса для хранения кода проверки.

public interface IValidator<T> 
{ 
    bool IsValid(T entity); 
    IEnumerable<string> BrokenRules(T entity); 
} 

public class OrderPersistenceValidator : IValidator<Order> 
{ 
    public bool IsValid(Order entity) 
    { 
     return BrokenRules(entity).Count() == 0; 
    } 

    public IEnumerable<string> BrokenRules(Order entity) 
    { 
     if (entity.Id < 0) 
      yield return "Id cannot be less than 0."; 

     if (string.IsNullOrEmpty(entity.Customer)) 
      yield return "Must include a customer."; 

     yield break; 
    } 
} 
  0

Подход, описанный в статье Богарда, выглядит очень удобно. Спасибо. 13 июн. 092009-06-13 17:08:29

  0

+1 Отличное решение 30 сен. 102010-09-30 16:25:05

+12

Одна вещь, однако, не должна строка 'return BrokenRules (entity) .Count()> 0' действительно быть' return BrokenRules (entity) .Count() <= 0'? 30 сен. 102010-09-30 19:12:45

+2

Я просто посмотрел сообщение Джимми и в основном собрал быстрый тест на месте, и я бы сказал, что одна вещь, которая мне не нравится в этом подходе, заключается в том, что вы реализуете ее с помощью методов расширения, например, в своей статье, когда потребитель называет «сущность» . », появляются методы TWO Validate(). Один, который сидит на объекте для реализации интерфейса IValidatable <T>, другой - метод расширения. Я потребляю объект с двумя методами .Validate(), я, вероятно, не знаю, какой из них нужно вызвать, не заглядывая в каждый метод. 01 мар. 122012-03-01 15:12:54

  0

Для entity.Customer нам нужно будет создать ClientValidator? 11 окт. 162016-10-11 12:24:41

  0

Не нарушает ли правило, что сущности домена всегда должны быть действительными? 16 мар. 172017-03-16 11:20:25


6

Я обычно работают на жидком использовать класс спецификации, предоставляет метод (это C#, но вы можете перевести его на любом языке):

bool IsVerifiedBy(TEntity candidate) 

Этот метод выполняет полную проверку кандидата и его отношений. Вы можете использовать аргументы в классе спецификации, чтобы сделать его параметризованным, как уровень проверки ...

Вы также можете добавить метод, чтобы узнать, почему кандидат не проверил спецификацию:

IEnumerable<string> BrokenRules(TEntity canditate) 

You может просто решить для реализации первого метода, как это:

bool IsVerifiedBy(TEntity candidate) 
{ 
    return BrokenRules(candidate).IsEmpty(); 
} 

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

IEnumerable<string> BrokenRules(TEntity candidate) 
{ 
    if (someComplexCondition) 
     yield return "Message describing cleary what is wrong..."; 
    if (someOtherCondition) 
     yield return 
    string.Format("The amount should not be {0} when the state is {1}", 
     amount, state); 
} 

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


54

Вместо того, чтобы полагаться на IsValid(xx) звонки по всему вашему приложению, рассмотреть вопрос о принятии некоторых советов от Greg Young:

Никогда не позволяйте вашей сущности попасть в недопустимом состоянии.

Это в основном означает, что вы переходите от мышления к объектам как к чистым контейнерам данных и больше об объектах с поведением.

Рассмотрим пример адреса человека:.

person.Address = "123 my street"; 
person.City = "Houston"; 
person.State = "TX"; 
person.Zip = 12345; 

между любым из этих вызовов ваш объект является недействительным (потому что вы бы иметь свойства, которые не согласуются друг с другом Теперь рассмотрим это:

person.ChangeAddress(.......); 

всех вызовов, связанных с поведением изменения адреса теперь атомная единица. Ваш объект никогда не недействителен здесь.

Если вы берете этот идентификатор ea поведения моделирования, а не состояния, то вы можете достичь модели, которая не допускает недопустимых объектов.

Для хорошей дискуссии по этому вопросу, проверить это InfoQ интервью: http://www.infoq.com/interviews/greg-young-ddd

+4

Я всегда думал, что совет Грега Янга, который вы опубликовали, никоим образом не практичен в реальном мире. 11 фев. 092009-02-11 13:21:49

+8

Он не просто отстаивает это, потому что это звучит неплохо, у него очень большие системы в этом мышлении. 13 фев. 092009-02-13 14:06:43

+1

Представьте, что есть объект, инкапсулирующий List <> вещей - 'Things'. На следующем этапе обработки, после того, как он был заполнен, я должен убедиться, что в списке есть определенный элемент, и это единственный его элемент. Как я могу заставить «Вещи» не попасть в недопустимое состояние? В качестве возможного решения я решил потребовать, чтобы пользователь поставил этот объект в качестве первого элемента списка, но я не могу этого сделать. Таким образом, объект «Вещи» может находиться в состоянии перехода, в котором он мог бы оставаться до начала обработки на следующем этапе. Вот почему я должен проверить его перед обработкой. 28 июл. 152015-07-28 15:03:55

+1

Я также склонен думать, что много бизнес-логики - это просто проверка (т. Е. До того, как мы изменим какое-то состояние, может ли эта операция быть выполнена?). DDD для сложной области. Иногда сложность - это просто сложная проверка в этой области. Вы принимаете это, и у вас есть субъект анемичного домена, и вам придется искать в другом месте, чтобы найти правила. Возможно, это оправдано позже, вытаскивая некоторые из правил, если сущность становится слишком большой. Но это можно сделать позже (чтобы избежать преждевременной оптимизации). Или, возможно, подумайте о ремоделировании совокупности. 27 авг. 152015-08-27 00:07:41

  0

Характеристики, похоже, больше для потребностей, которые сущность имеет, но не может выполнить в своем собственном домене. Вместо того, чтобы вводить двунаправленный доступ к совокупности с внешним миром, предприятие может просто предоставить спецификацию. Служба домена может принять это и выполнить (возможно, делать вызовы репозитория) и вернуть что-то вроде объекта Value. Затем VO может быть передано в конструкцию/метод на сущности для удовлетворения требуемой потребности. 27 авг. 152015-08-27 00:15:39

  0

@Pixar Это 2 разных списка! Первый список с инвариантным элементом _any равен ok_, второй список - с инвариантом _has определенного элемента, и он является единственным элементом его kind_.На следующем этапе обработки, после заполнения 1-го списка_, специальный бизнес-метод пытается заполнить второй список из 1-го. Ни один из списков не попадает в недействительное/переходное состояние! 01 авг. 162016-08-01 12:32:00


-1

Этот вопрос немного устарел, но в случае, если кому-то интересно, вот как я реализую проверку в своих классах обслуживания.

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

Пример DocumentService со встроенной проверкой

Мне нравится этот подход, поскольку он позволяет мне поставить всю свою логику проверки ядра в одном месте, которое держит вещи простыми.


-1

В java мире вы должны рассмотреть возможность использования hibernate validation

Это очень читаемым для простых валидаций

public class Car { 

    @NotNull 
    private String manufacturer; 

    @NotNull 
    @Size(min = 2, max = 14) 
    private String licensePlate; 

    @Min(2) 
    private int seatCount; 

    // ... 
} 

Что касается комплексных проверок. Существует mechanism, чтобы написать свои собственные проверки.

package org.hibernate.validator.referenceguide.chapter02.classlevel; 

@ValidPassengerCount 
public class Car { 

    private int seatCount; 

    private List<Person> passengers; 

    //... 
}