Zusammenfassung
Sollte die Autorisierung in CQRS / DDD per Befehl / Abfrage implementiert werden oder nicht?
Ich entwickle zum ersten Mal eine Online-Bewerbung unter mehr oder weniger strikter Verwendung des DDD-CQRS-Musters. Ich bin auf ein Problem gestoßen, mit dem ich mich nicht wirklich auskennen kann.
Die Anwendung, die ich erstelle, ist eine Hauptbuchanwendung, die es Benutzern ermöglicht, Hauptbücher zu erstellen und anderen Benutzern zu ermöglichen, diese anzuzeigen, zu bearbeiten oder zu löschen, z. B. Mitarbeitern. Der Ersteller eines Ledgers sollte in der Lage sein, die Zugriffsrechte des von ihm erstellten Ledgers zu bearbeiten. Könnte sogar den Besitzer wechseln. Die Domain hat zwei Aggregate TLedger und TUser .
Ich habe viele Posts mit dem DDD / CQRS-Schlüsselwort zu Sicherheit, Autorisierung usw. gelesen. Die meisten gaben an, dass Autorisierung eine generische Subdomäne ist , es sei denn, man erstellt eine Sicherheitsanwendung.
In diesem Fall ist die Kerndomäne sicherlich eine Buchhaltungsdomäne, die sich für Transaktionen, Bilanzierung und Konten interessiert. Es ist jedoch auch die Funktionalität erforderlich, den differenzierten Zugriff auf die Hauptbücher verwalten zu können. Ich frage mich, wie ich dies in DDD / CQRS-Begriffen gestalten soll.
In den DDD-Tutorials heißt es überall, dass die Befehle Teil der allgegenwärtigen Sprache sind. Sie sind sinnvoll. Es sind konkrete Aktionen, die die "reale Sache" darstellen.
Da all diese Befehle und Abfragen tatsächliche Aktionen sind, die Benutzer im "echten Leben" ausführen würden, sollte die Implementierung der Berechtigung mit all diesen "Befehlen" und "Abfragen" gekoppelt werden? Ein Benutzer hätte beispielsweise die Berechtigung, TLedger.addTransaction () auszuführen, nicht jedoch TLedger.removeTransaction (). Oder ein Benutzer darf die Abfrage "getSummaries ()" ausführen, nicht jedoch "getTransactions ()".
Eine dreidimensionale Abbildung würde in Form eines Benutzer-Hauptbuch-Befehls oder einer Benutzer-Hauptbuch-Abfrage existieren, um die Zugriffsrechte zu bestimmen.
Oder in entkoppelter Weise werden benannte "Berechtigungen" für einen Benutzer registriert. Berechtigungen, die dann für bestimmte Befehle zugeordnet würden. Mit der Berechtigung "ManageTransactions" kann ein Benutzer beispielsweise "AddTransaction ()", "RemoveTransaction ()" usw. ausführen.
Berechtigungszuordnung Benutzer -> Ledger -> Befehl / Abfrage
Berechtigungszuordnung Benutzer -> Hauptbuch -> Berechtigung -> Befehl / Abfrage
Das ist der erste Teil der Frage. Oder kurz gesagt, sollte die Autorisierung in CQRS / DDD pro Befehl oder pro Abfrage implementiert werden? Oder sollte die Autorisierung von den Befehlen entkoppelt werden?
Zweitens in Bezug auf die Autorisierung basierend auf Berechtigungen. Ein Benutzer sollte in der Lage sein, die Berechtigungen für seine Hauptbücher oder für die Hauptbücher, die er verwalten durfte, zu verwalten.
- Befehle zur Berechtigungsverwaltung werden im Ledger ausgeführt
Ich dachte an das Hinzufügen der Ereignisse / Befehle / Handler zum Ledger- Aggregat, z. B. grantPermission (), revokePermission () usw. In diesem Fall würde die Durchsetzung dieser Regeln in den Befehlshandlern erfolgen. Dies würde jedoch erfordern, dass alle Befehle die ID des Benutzers enthalten, der diesen Befehl ausgegeben hat. Dann würde ich in den TLedger einchecken, wenn die Berechtigung für diesen Benutzer besteht, diesen Befehl auszuführen.
Zum Beispiel :
class TLedger{
function addTransactionCmdHandler(cmd){
if (!this.permissions.exist(user, 'addTransaction')
throw new Error('Not Authorized');
}
}
- Berechtigungsverwaltungsbefehle im Benutzer
Der andere Weg wäre, die Berechtigungen in den TUser aufzunehmen. Ein TUser hätte eine Reihe von Berechtigungen. Dann würde ich in den TLedger-Befehlshandlern den Benutzer abrufen und prüfen, ob er die Berechtigung hat, den Befehl auszuführen. Dafür müsste ich jedoch das TUser-Aggregat für jeden TLedger-Befehl abrufen.
class TAddTransactionCmdHandler(cmd) {
this.userRepository.find(cmd.userId)
.then(function(user){
if (!user.can(cmd)){
throw new Error('Not authorized');
}
return this.ledgerRepository.find(cmd.ledgerId);
})
.then(function(ledger){
ledger.addTransaction(cmd);
})
}
- Eine andere Domain mit Service
Eine andere Möglichkeit wäre, eine andere Berechtigungsdomäne vollständig zu modellieren. Diese Domain würde sich für Zugriffsrechte, Autorisierung usw. interessieren. Die Subdomain Accounting würde dann einen Dienst verwenden, um auf diese Autorisierungsdomain in Form von zuzugreifen AuthorizationService.isAuthorized(user, command)
.
class TAddTransactionCmdHandler(cmd) {
authService.isAuthorized(cmd)
.then(function(authorized){
if (!authorized) throw new Error('Not authorized');
return this.ledgerRepository.find(cmd.ledgerId)
})
.then(function(){
ledger.addTransaction(cmd);
})
}
Welche Entscheidung wäre die "DDD / CQRS" -Methode?
quelle
Antworten:
Bei der ersten Frage habe ich mit etwas Ähnlichem zu kämpfen. Ich neige immer mehr zu einem dreiphasigen Genehmigungsschema:
1) Berechtigung auf Befehls- / Abfrageebene von "Hat dieser Benutzer jemals die Berechtigung, diesen Befehl auszuführen?" In einer MVC-App könnte dies wahrscheinlich auf Controller-Ebene erfolgen, aber ich wähle einen generischen Pre-Handler, der den Berechtigungsspeicher basierend auf dem aktuellen Benutzer und dem ausgeführten Befehl abfragt.
2) Die Autorisierung innerhalb des Anwendungsdienstes von "Hat dieser Benutzer" jemals * die Berechtigung, auf diese Entität zuzugreifen? "In meinem Fall handelt es sich wahrscheinlich um eine implizite Prüfung, die einfach durch Filter im Repository erfolgt. In meiner Domäne ist dies Im Grunde genommen eine TenantId mit etwas mehr Granularität der OrganizationId.
3) Die Autorisierung, die von vorübergehenden Eigenschaften Ihrer Entitäten abhängt (z. B. Status), würde innerhalb der Domäne erfolgen. (Bsp. "Nur bestimmte Personen können ein geschlossenes Hauptbuch ändern.") Ich entscheide mich dafür, das in die Domäne einzufügen, da es stark von der Domäne und der Geschäftslogik abhängt, und es ist mir nicht wirklich angenehm, das an anderen Stellen zu offenbaren.
Ich würde gerne die Reaktionen anderer auf diese Idee hören - reißen Sie sie in Stücke, wenn Sie wollen (geben Sie nur einige Alternativen an, wenn Sie dies tun :))
quelle
Ich würde die Autorisierung als Teil Ihres Autorisierungs-BC implementieren, sie jedoch als Aktionsfilter in Ihrem Ledger-System bereitstellen. Auf diese Weise können sie logisch voneinander entkoppelt werden - Ihr Ledger-Code sollte den Autorisierungscode nicht aufrufen müssen -, aber Sie erhalten dennoch eine hochperformante In-Process-Autorisierung für jede eingehende Anforderung.
quelle