So che stai pensando (o forse urlando), "non un'altra domanda che ti chiede dove appartiene la validazione in un'architettura a strati?!?" Bene, sì, ma spero che questo sia un po 'diverso dall'argomento.
Sono fermamente convinto che la convalida abbia molte forme, sia basata sul contesto e vari a ogni livello dell'architettura. Questa è la base per il post - che aiuta a identificare quale tipo di validazione dovrebbe essere eseguita in ogni livello. Inoltre, una domanda che spesso si pone è dove appartengono i controlli di autorizzazione.
Lo scenario di esempio proviene da un'applicazione per un'azienda di catering. Periodicamente durante il giorno, un conducente può consegnare all'ufficio qualsiasi eccesso di denaro accumulato durante il trasporto del camion da un sito all'altro. L'applicazione consente a un utente di registrare il "calo di cassa" raccogliendo l'ID del conducente e l'importo. Ecco un po 'di codice scheletro per illustrare i livelli coinvolti:
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
...
}
}
Ho indicato 10 posizioni in cui ho visto i controlli di convalida inseriti nel codice. La mia domanda è quali controlli dovresti eseguire, se presenti, in base alle seguenti regole aziendali (insieme ai controlli standard per lunghezza, intervallo, formato, tipo, ecc.):
- L'importo del calo di cassa deve essere maggiore di zero.
- Il drop in contanti deve avere un driver valido.
- L'utente corrente deve essere autorizzato ad aggiungere gocce di denaro (l'utente corrente non è il conducente).
Per favore, condividi i tuoi pensieri, come hai o ti avvicinerai a questo scenario e le ragioni delle tue scelte.
CashDropAmount
oggetto valore piuttosto che usare a Decimal
. Controllare se il driver esiste o no verrebbe eseguito nel gestore dei comandi e lo stesso vale per le regole di autorizzazione. È possibile ottenere l'autorizzazione gratuitamente facendo qualcosa di simile al punto in Approver approver = approverService.findById(employeeId)
cui viene lanciato se il dipendente non ricopre il ruolo di responsabile dell'approvazione. Approver
sarebbe solo un oggetto valore, non un'entità. Si potrebbe anche sbarazzarsi del vostro metodo di fabbrica in fabbrica o l'uso su un AR invece: cashDrop = driver.dropCash(...)
.