Je sais que vous pensez (ou criez peut-être), "pas une autre question demandant où appartient la validation dans une architecture en couches?!?" Eh bien, oui, mais j'espère que ce sera un peu une vision différente du sujet.
Je suis fermement convaincu que la validation prend de nombreuses formes, est basée sur le contexte et varie à chaque niveau de l'architecture. C'est la base pour le post - aider à identifier quel type de validation doit être effectué dans chaque couche. De plus, une question qui revient souvent est celle de savoir où appartiennent les contrôles d'autorisation.
Le scénario d'exemple provient d'une application pour une entreprise de restauration. Périodiquement pendant la journée, un chauffeur peut remettre au bureau tout excédent d'argent qu'il a accumulé en transportant le camion d'un site à l'autre. L'application permet à un utilisateur d'enregistrer la «goutte d'argent» en collectant l'ID du conducteur et le montant. Voici un code squelette pour illustrer les couches impliquées:
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
...
}
}
J'ai indiqué 10 emplacements où j'ai vu des contrôles de validation placés dans le code. Ma question est de savoir quelles vérifications vous feriez, le cas échéant, pour chacune des règles commerciales suivantes (avec les vérifications standard pour la longueur, la plage, le format, le type, etc.):
- Le montant de la remise en espèces doit être supérieur à zéro.
- La remise en espèces doit avoir un chauffeur valide.
- L'utilisateur actuel doit être autorisé à ajouter des espèces (l'utilisateur actuel n'est pas le conducteur).
Veuillez partager vos réflexions, comment vous envisagez ou envisagez ce scénario et les raisons de vos choix.
CashDropAmount
objet de valeur plutôt qu'en utilisant un Decimal
. Vérifier si le pilote existe ou non serait fait dans le gestionnaire de commandes et il en va de même pour les règles d'autorisation. Vous pouvez obtenir une autorisation gratuitement en faisant quelque chose comme Approver approver = approverService.findById(employeeId)
où elle est lancée si l'employé n'est pas dans le rôle d'approbateur. Approver
serait juste un objet de valeur, pas une entité. Vous pouvez également vous débarrasser de votre usine ou utiliser la méthode de l' usine sur un AR à la place: cashDrop = driver.dropCash(...)
.