Вопрос

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

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

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

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

Это было полезно?

Решение

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

Другие советы

Я всегда слышу аргументы людей в пользу метода «Проверить» или «Исдействителен».

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

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

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

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

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

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

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

Одним из решений является использование 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().

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

Внутри он будет содержать коллекцию валидаторов и очень простой набор общедоступных методов, таких как 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.

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top