Wiem, że myślisz (a może krzyczysz): „nie ma innego pytania, gdzie należy sprawdzić poprawność w architekturze warstwowej?!?” Cóż, tak, ale mam nadzieję, że będzie to trochę inne spojrzenie na ten temat.
Jestem głęboko przekonany, że sprawdzanie poprawności przybiera wiele form, jest oparte na kontekście i różni się na każdym poziomie architektury. To jest podstawa do postu - pomaga określić, jaki rodzaj walidacji powinien zostać wykonany na każdej warstwie. Ponadto często pojawia się pytanie, gdzie należą kontrole autoryzacji.
Przykładowy scenariusz pochodzi z aplikacji dla firmy cateringowej. Okresowo w ciągu dnia kierowca może zwrócić się do biura z nadwyżką gotówki zgromadzonej podczas transportu ciężarówki z miejsca na miejsce. Aplikacja pozwala użytkownikowi zarejestrować „wypłatę gotówki” poprzez zebranie identyfikatora kierowcy i kwoty. Oto szkielet kodu ilustrujący zaangażowane warstwy:
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
...
}
}
Wskazałem 10 lokalizacji, w których widziałem sprawdzanie poprawności umieszczone w kodzie. Moje pytanie dotyczy tego, jakie kontrole przeprowadzilibyście przy każdej z następujących reguł biznesowych (wraz ze standardowymi kontrolami długości, zakresu, formatu, typu itp.):
- Kwota zrzutu gotówki musi być większa od zera.
- Upuszczenie gotówki musi mieć ważnego Kierowcę.
- Bieżący użytkownik musi być upoważniony do dodawania zrzutów gotówki (bieżący użytkownik nie jest kierowcą).
Proszę podzielić się swoimi przemyśleniami, jak masz lub podejmiesz ten scenariusz i powody swoich wyborów.
CashDropAmount
obiekt wartości zamiast używać Decimal
. Sprawdzenie, czy sterownik istnieje, czy nie, zostanie wykonane w module obsługi poleceń i to samo dotyczy reguł autoryzacji. Możesz uzyskać autoryzację za darmo, robiąc coś w taki sposób, w Approver approver = approverService.findById(employeeId)
jaki wyrzuca, jeśli pracownik nie pełni roli osoby zatwierdzającej. Approver
byłby tylko obiektem wartości, a nie bytem. Można też pozbyć się fabryki lub użyć metody fabryki na AR zamiast: cashDrop = driver.dropCash(...)
.