Как отделить проверку данных от моих простых доменных объектов (POCO)? - PullRequest
7 голосов
/ 03 января 2009

Этот вопрос не зависит от языка, но я парень на C #, поэтому я использую термин POCO для обозначения объекта, который только преформирует хранилище данных, обычно используя поля getter и setter.

Я просто переделал мою модель предметной области, чтобы она стала супер-точной POCO, и у меня осталась пара вопросов о том, как обеспечить, чтобы значения свойств имели смысл в домене.

Например, Конечная дата Сервиса не должна превышать Конечную дату Контракта, на котором находится Сервис. Тем не менее, кажется, что нарушение SOLID ставит проверку в установщик Service.EndDate, не говоря уже о том, что по мере увеличения числа проверок мои классы POCO будут загромождены.

У меня есть некоторые решения (будут публиковаться в ответах), но у них есть свои недостатки, и мне интересно, каковы некоторые из любимых подходов к решению этой дилеммы?

Ответы [ 8 ]

6 голосов
/ 03 января 2009

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

3 голосов
/ 14 октября 2009

У моего коллеги возникла идея, которая сработала довольно хорошо. У нас никогда не было подходящего имени, но мы называли его «Инспектор / Судья».

Инспектор будет смотреть на объект и сообщать вам все правила, которые он нарушил. Судья решит, что с этим делать. Это разделение позволило нам сделать несколько вещей. Это позволило нам разместить все правила в одном месте (инспектор), но мы могли бы иметь несколько судей и выбирать судью по контексту.

Один из примеров использования нескольких судей вращается вокруг правила, согласно которому Клиент должен иметь адрес. Это было стандартное трехуровневое приложение. На уровне пользовательского интерфейса судья должен производить что-то, что пользовательский интерфейс может использовать для указания полей, которые должны быть заполнены. Судья пользовательского интерфейса не выбрасывает исключения. На уровне обслуживания был еще один судья. Если во время сохранения он найдет клиента без адреса, он выдаст исключение. В этот момент вы действительно должны остановить процесс.

У нас также были Судьи, которые были более строгими, так как состояние объектов менялось. Это была заявка на страхование, и в процессе цитирования полису разрешалось сохранять в неполном состоянии. Но как только эта Политика была готова к тому, чтобы стать Активной, многое нужно было установить. Таким образом, судья-заявитель со стороны службы был не так строг, как судья-активатор. Тем не менее, правила, используемые в Инспекторе, были все те же, так что вы все равно могли сказать, что было неполным, даже если вы решили ничего с этим не делать.

3 голосов
/ 03 января 2009

Я всегда слышу, как люди аргументируют метод "Validate" или "IsValid".

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

Поэтому я предпочитаю «IsValidForNewContract», «IsValidForTermination» или аналогичные, потому что я считаю, что большинство проектов в конечном итоге имеют несколько таких валидаторов / состояний для каждого класса. Это также означает, что у меня нет интерфейса, но я могу написать агрегированные валидаторы, которые read очень хорошо отражают условия бизнеса, которые я утверждаю.

Я действительно верю, что универсальные решения в этом случае очень часто отвлекаются от того, что важно - что делает код - для очень незначительного выигрыша в технической элегантности (интерфейс, делегат или что-то еще) , Просто проголосуй за это;)

2 голосов
/ 03 января 2009

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

Внутренне это будет содержать коллекцию Validators и очень простой набор открытых методов, таких как Validate (), которые могут возвращать коллекцию объекта ошибки.

Очень просто, как-то так в C #

public class ValidationService<T>
{
  private IList<IValidator> _validators;

  public IList<Error> Validate(T objectToValidate)
  {
    foreach(IValidator validator in _validators)
    {
      yield return validator.Validate(objectToValidate);
    }
  }
}

Валидаторы могут быть добавлены в конструктор по умолчанию или внедрены через какой-либо другой класс, например ValidationServiceFactory.

2 голосов
/ 03 января 2009

Одно из решений состоит в том, чтобы каждый объект DataAccessObject получал список валидаторов. Когда вызывается Save, выполняется предварительная проверка каждого валидатора:

public class ServiceEndDateValidator : IValidator<Service> {
  public void Check(Service s) {
    if(s.EndDate > s.Contract.EndDate)
      throw new InvalidOperationException();
  }
}

public class ServiceDao : IDao<Service> {
  IValidator<Service> _validators;
  public ServiceDao(IEnumerable<IValidator<Service>> validators) {_validators = validators;}
  public void Save(Service s) {
    foreach(var v in _validators)
      v.Check(service);
    // Go on to save
  }
}

Преимущество, очень понятный SoC, недостаток в том, что мы не получим чек, пока не будет вызван Save ().

0 голосов
/ 04 января 2009

Вот еще одна возможность. Проверка выполняется через прокси или декоратор объекта Domain:

public class ServiceValidationProxy : Service {
  public override DateTime EndDate {
    get {return EndDate;}
    set {
      if(value > Contract.EndDate)
        throw new InvalidOperationexception();
      base.EndDate = value;
    }
  }
}

Преимущество: мгновенная проверка. Легко настраивается через IoC.

Недостаток: если прокси, проверенные свойства должны быть виртуальными, если в декораторе все доменные модели должны быть основаны на интерфейсе. Классы проверки окажутся немного тяжелыми - прокси должны наследовать класс, а декораторы должны реализовать все методы. Имена и организация могут запутаться.

0 голосов
/ 03 января 2009

Другая возможность состоит в том, чтобы каждый из моих классов реализовывал

public interface Validatable<T> {
  public event Action<T> RequiresValidation;
}

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

Преимущество - проверка в реальном времени. Но более сложный код и неясно, кто должен делать присоединение.

0 голосов
/ 03 января 2009

Я думаю, что это, пожалуй, лучшее место для логики, но это только я. У вас может быть какой-то метод IsValid, который также проверяет все условия и возвращает true / false, может быть, это своего рода коллекция ErrorMessages, но это сомнительная тема, поскольку сообщения об ошибках на самом деле не являются частью модели предметной области. Я немного предвзят, поскольку проделал некоторую работу с RoR, и это, по сути, то, что делают его модели.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...