Проверка в проекте, управляемом доменом
Как вы справляетесь с проверкой сложных агрегатов в проекте, управляемом доменом? Укрепляете ли вы свои бизнес-правила/логику проверки?
Я понимаю аргументацию. И я понимаю проверку свойств, которая может быть привязана к самим моделям, и делать такие вещи, как проверка того, что адрес электронной почты или почтовый индекс действителен или что имя имеет минимальную и максимальную длину.
Но как насчет комплексной проверки, которая включает несколько моделей? Где вы обычно размещаете эти правила и методы в своей архитектуре? И какие шаблоны, если они используются для их реализации?
Ответы
Ответ 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;
//...
}