Подтвердить что ты не робот

Проверка в проекте, управляемом доменом

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

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

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

4b9b3361

Ответ 1

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

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;
    }
}

Ответ 2

Вместо того, чтобы полагаться на вызовы IsValid(xx) по всему вашему приложению, подумайте о том, чтобы принять некоторые рекомендации от Грега Янга:

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

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

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

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

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

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

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

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

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

Ответ 3

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

bool IsVerifiedBy(TEntity candidate)

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

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

IEnumerable<string> BrokenRules(TEntity canditate) 

Вы можете просто решить реализовать первый метод следующим образом:

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. Я помещаю эти классы в пространство имен модели с именами, которые предполагают их использование.

Ответ 4

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

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

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

public class DocumentService : IDocumentService
{
    private IRepository<Document> _documentRepository;

    public DocumentService(IRepository<Document> documentRepository)
    {
        _documentRepository = documentRepository;
    }

    public void Create(Document document)
    {
        Validate(document, Action.Create);

        document.CreatedDate = DateTime.Now;

        _documentRepository.Create(document);
    }

    public void Update(Document document)
    {
        Validate(document, Action.Update);

        _documentRepository.Update(document);
    }

    public void Delete(int id)
    {
        Validate(_documentRepository.GetById(id), Action.Delete);

        _documentRepository.Delete(id);
    }

    public IList<Document> GetAll()
    {
        return _documentRepository
            .GetAll()
            .OrderByDescending(x => x.PublishDate)
            .ToList();
    }

    public int GetAllCount()
    {
        return _documentRepository
            .GetAll()
            .Count();
    }

    public Document GetById(int id)
    {
        return _documentRepository.GetById(id);
    }

    // validation 

    private void Validate(Document document, Action action)
    {
        var brokenRules = new List<string>();

        if (action == Action.Create || action == Action.Update)
        {
            if (string.IsNullOrWhiteSpace(document.Title))
                brokenRules.Add("Title is required");

            if (document.PublishDate == null)
                brokenRules.Add("Publish Date is required");
        }

        if (brokenRules.Any())
            throw new EntityException(string.Join("\r\n", brokenRules));
    }

    private enum Action
    {
        Create,
        Update,
        Delete
    }
}

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

Ответ 5

В мире java вам следует использовать проверку спящего режима

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

public class Car {

   @NotNull
   private String manufacturer;

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

   @Min(2)
   private int seatCount;

   // ...
}

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

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

@ValidPassengerCount
public class Car {

    private int seatCount;

    private List<Person> passengers;

    //...
}