Я знаю, что вы думаете (или, возможно, кричите), "не другой вопрос, спрашивающий, где валидация относится к многоуровневой архитектуре?!?" Ну, да, но, надеюсь, это будет немного другой взгляд на эту тему.
Я твердо убежден в том, что валидация принимает разные формы, основана на контексте и варьируется на каждом уровне архитектуры. Это основа для пост - помогает определить, какой тип проверки должен быть выполнен на каждом уровне. Кроме того, часто возникает вопрос: где принадлежат проверки авторизации?
Пример сценария происходит из приложения для кейтерингового бизнеса. Периодически в течение дня водитель может сдавать в офис любые лишние денежные средства, накопленные им при доставке грузовика с места на место. Приложение позволяет пользователю записать «денежное сбрасывание» путем сбора идентификатора водителя и суммы. Вот некоторый скелетный код для иллюстрации задействованных слоев:
public class CashDropApi // This is in the Service Facade Layer
{
[WebInvoke(Method = "POST")]
public void AddCashDrop(NewCashDropContract contract)
{
// 1
Service.AddCashDrop(contract.Amount, contract.DriverId);
}
}
public class CashDropService // This is the Application Service in the Domain Layer
{
public void AddCashDrop(Decimal amount, Int32 driverId)
{
// 2
CommandBus.Send(new AddCashDropCommand(amount, driverId));
}
}
internal class AddCashDropCommand // This is a command object in Domain Layer
{
public AddCashDropCommand(Decimal amount, Int32 driverId)
{
// 3
Amount = amount;
DriverId = driverId;
}
public Decimal Amount { get; private set; }
public Int32 DriverId { get; private set; }
}
internal class AddCashDropCommandHandler : IHandle<AddCashDropCommand>
{
internal ICashDropFactory Factory { get; set; } // Set by IoC container
internal ICashDropRepository CashDrops { get; set; } // Set by IoC container
internal IEmployeeRepository Employees { get; set; } // Set by IoC container
public void Handle(AddCashDropCommand command)
{
// 4
var driver = Employees.GetById(command.DriverId);
// 5
var authorizedBy = CurrentUser as Employee;
// 6
var cashDrop = Factory.CreateCashDrop(command.Amount, driver, authorizedBy);
// 7
CashDrops.Add(cashDrop);
}
}
public class CashDropFactory
{
public CashDrop CreateCashDrop(Decimal amount, Employee driver, Employee authorizedBy)
{
// 8
return new CashDrop(amount, driver, authorizedBy, DateTime.Now);
}
}
public class CashDrop // The domain object (entity)
{
public CashDrop(Decimal amount, Employee driver, Employee authorizedBy, DateTime at)
{
// 9
...
}
}
public class CashDropRepository // The implementation is in the Data Access Layer
{
public void Add(CashDrop item)
{
// 10
...
}
}
Я указал 10 мест, где я видел проверки в коде. Мой вопрос заключается в том, какие проверки вы, если таковые имеются, выполняете для каждого из следующих бизнес-правил (наряду со стандартными проверками длины, диапазона, формата, типа и т. Д.):
- Сумма сброса должна быть больше нуля.
- Денежный перевод должен иметь действующего водителя.
- Текущий пользователь должен быть авторизован для добавления денежных средств (текущий пользователь не является водителем).
Пожалуйста, поделитесь своими мыслями, как у вас есть или будет подходить к этому сценарию и причины вашего выбора.
CashDropAmount
значения, используя объект-значение вместо использования Decimal
. Проверка наличия или отсутствия драйвера будет выполнена в обработчике команд, и то же самое касается правил авторизации. Вы можете получить авторизацию бесплатно, выполнив что-то наподобие того, Approver approver = approverService.findById(employeeId)
где она выдает, если сотрудник не в роли утверждающего. Approver
будет просто объект значения, а не сущность. Вы также могли бы избавиться от фабрики или использования фабричного метода на AR вместо: cashDrop = driver.dropCash(...)
.