Ich weiß, dass Sie denken (oder vielleicht schreien), "nicht eine andere Frage, wo Validierung in einer geschichteten Architektur gehört?!?" Nun ja, aber hoffentlich wird dies eine etwas andere Sicht auf das Thema sein.
Ich bin der festen Überzeugung, dass die Validierung viele Formen annimmt, kontextabhängig ist und auf jeder Ebene der Architektur variiert. Auf dieser Grundlage kann nachträglich ermittelt werden, welche Art von Validierung in den einzelnen Ebenen durchgeführt werden soll. Außerdem stellt sich häufig die Frage, wo Berechtigungsprüfungen angesiedelt sind.
Das Beispielszenario stammt aus einer Anwendung für ein Catering-Unternehmen. Während des Tages kann ein Fahrer in regelmäßigen Abständen überschüssiges Bargeld im Büro abgeben, das er gesammelt hat, während er den Lastwagen von Ort zu Ort gebracht hat. Die Anwendung ermöglicht es einem Benutzer, den "Geldverlust" durch Sammeln des Fahrerausweises und des Betrags aufzuzeichnen. Hier ist ein Code, der die beteiligten Ebenen veranschaulicht:
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
...
}
}
Ich habe 10 Stellen angegeben, an denen ich Validierungsprüfungen im Code gesehen habe. Meine Frage ist, welche Überprüfungen Sie gegebenenfalls bei den folgenden Geschäftsregeln durchführen würden (zusammen mit Standardüberprüfungen für Länge, Bereich, Format, Typ usw.):
- Der Betrag des Bargeldabfalls muss größer als Null sein.
- Die Kasse muss einen gültigen Fahrer haben.
- Der aktuelle Benutzer muss berechtigt sein, Geldeinzahlungen hinzuzufügen (der aktuelle Benutzer ist nicht der Fahrer).
Bitte teilen Sie Ihre Gedanken mit, wie Sie dieses Szenario angehen oder angehen würden und welche Gründe für Ihre Wahl sprechen.
CashDropAmount
Wertobjekt haben, anstatt ein Decimal
. Die Überprüfung, ob der Treiber vorhanden ist oder nicht, wird im Befehlshandler durchgeführt, und dies gilt auch für die Autorisierungsregeln. Sie können die Autorisierung kostenlos erhalten, indem Approver approver = approverService.findById(employeeId)
Sie eine Aktion ausführen, bei der der Mitarbeiter nicht die Rolle des Genehmigenden übernimmt. Approver
wäre nur ein Wertobjekt, keine Entität. Sie können auch Ihre Fabrik oder Verwendung Factory - Methode auf einem AR statt loszuwerden: cashDrop = driver.dropCash(...)
.