Ich versuche, eine Anwendung zu entwerfen, die eine komplexe Geschäftsdomäne aufweist und eine REST-API unterstützen muss (nicht ausschließlich REST, sondern ressourcenorientiert). Ich habe einige Probleme damit, das Domänenmodell ressourcenorientiert darzustellen.
In DDD müssen Clients eines Domänenmodells die prozedurale Ebene "Application Services" durchlaufen, um auf alle Geschäftsfunktionen zuzugreifen, die von Entities und Domain Services implementiert werden. Beispielsweise gibt es einen Anwendungsdienst mit zwei Methoden zum Aktualisieren einer Benutzerentität:
userService.ChangeName(name);
userService.ChangeEmail(email);
Die API dieses Anwendungsdienstes macht Befehle (Verben, Prozeduren) verfügbar, nicht den Status.
Wenn wir jedoch auch eine RESTful-API für dieselbe Anwendung bereitstellen müssen, gibt es ein Benutzerressourcenmodell, das wie folgt aussieht:
{
name:"name",
email:"[email protected]"
}
Die ressourcenorientierte API macht den Status und keine Befehle verfügbar . Dies wirft folgende Bedenken auf:
Jeder Aktualisierungsvorgang für eine REST-API kann einem oder mehreren Application Service-Prozeduraufrufen zugeordnet werden, je nachdem, welche Eigenschaften im Ressourcenmodell aktualisiert werden
Jeder Aktualisierungsvorgang sieht für den REST-API-Client atomar aus, ist jedoch nicht so implementiert. Jeder Aufruf von Application Service ist als separate Transaktion konzipiert. Das Aktualisieren eines Felds in einem Ressourcenmodell kann die Validierungsregeln für andere Felder ändern. Wir müssen daher alle Ressourcenmodellfelder gemeinsam validieren, um sicherzustellen, dass alle potenziellen Application Service-Aufrufe gültig sind, bevor wir mit deren Erstellung beginnen. Das gleichzeitige Überprüfen einer Reihe von Befehlen ist weitaus weniger trivial als das gleichzeitige Ausführen von Befehlen. Wie machen wir das auf einem Client, der nicht einmal weiß, dass einzelne Befehle existieren?
Das Aufrufen von Application Service-Methoden in unterschiedlicher Reihenfolge hat möglicherweise einen anderen Effekt, während die REST-API den Eindruck erweckt, dass es keinen Unterschied gibt (innerhalb einer Ressource).
Ich könnte mir ähnliche Probleme ausdenken, aber im Grunde genommen werden sie alle durch dasselbe verursacht. Nach jedem Aufruf eines Anwendungsdienstes ändert sich der Status des Systems. Regeln für die gültige Änderung, die Menge der Aktionen, die eine Entität bei der nächsten Änderung ausführen kann. Eine ressourcenorientierte API versucht, alles wie eine atomare Operation aussehen zu lassen. Aber die Komplexität, diese Lücke zu überwinden, muss irgendwohin gehen, und es scheint gewaltig.
Wenn die Benutzeroberfläche mehr befehlsorientiert ist, was häufig der Fall ist, müssen wir außerdem auf der Clientseite eine Zuordnung zwischen Befehlen und Ressourcen vornehmen und dann wieder auf der API-Seite.
Fragen:
- Sollte all diese Komplexität nur von einer (dicken) REST-zu-AppService-Zuordnungsebene bewältigt werden?
- Oder vermisse ich etwas in meinem Verständnis von DDD / REST?
- Könnte REST einfach nicht praktikabel sein, um die Funktionalität von Domänenmodellen über einen bestimmten (relativ geringen) Komplexitätsgrad hinweg verfügbar zu machen?
quelle
Antworten:
Ich hatte das gleiche Problem und habe es "gelöst", indem ich REST-Ressourcen unterschiedlich modelliert habe, zB:
Deshalb habe ich die größere, komplexe Ressource in mehrere kleinere aufgeteilt. Jede von diesen enthält eine etwas zusammenhängende Gruppe von Attributen der ursprünglichen Ressource, von denen erwartet wird, dass sie zusammen verarbeitet werden.
Jede Operation auf diesen Ressourcen ist atomar, auch wenn sie mit mehreren Dienstmethoden implementiert werden kann - zumindest in Spring / Java EE ist es kein Problem, eine größere Transaktion aus mehreren Methoden zu erstellen, für die ursprünglich eine eigene Transaktion vorgesehen war (mit REQUIRED-Transaktion) Vermehrung). Sie müssen für diese spezielle Ressource häufig noch eine zusätzliche Validierung durchführen, diese ist jedoch immer noch überschaubar, da die Attribute zusammenhängend sind (sein sollen).
Dies ist auch für den HATEOAS-Ansatz von Vorteil, da Ihre feinkörnigeren Ressourcen mehr Informationen darüber liefern, was Sie mit ihnen tun können (anstatt diese Logik auf Client und Server zu haben, da sie nicht einfach in Ressourcen dargestellt werden kann).
Es ist natürlich nicht perfekt - wenn Benutzeroberflächen nicht unter Berücksichtigung dieser Ressourcen modelliert werden (insbesondere datenorientierte Benutzeroberflächen), kann dies zu Problemen führen - z. B. zeigt die Benutzeroberfläche eine große Form aller Attribute bestimmter Ressourcen (und ihrer Subressourcen) und ermöglicht dies Bearbeiten Sie sie alle und speichern Sie sie auf einmal - dies erzeugt die Illusion von Atomizität, obwohl der Client mehrere Ressourcenoperationen aufrufen muss (die selbst atomar sind, die gesamte Sequenz jedoch nicht atomar).
Auch diese Aufteilung der Ressourcen ist manchmal nicht einfach oder offensichtlich. Ich mache dies hauptsächlich an Ressourcen mit komplexen Verhaltensweisen / Lebenszyklen, um deren Komplexität zu managen.
quelle
Das zentrale Problem hierbei ist, wie die Geschäftslogik transparent aufgerufen wird, wenn ein REST-Aufruf erfolgt. Dies ist ein Problem, das von REST nicht direkt behoben wird.
Ich habe dieses Problem gelöst, indem ich über einen Persistenzanbieter wie JPA eine eigene Datenverwaltungsebene erstellt habe. Mithilfe eines Metamodells mit benutzerdefinierten Anmerkungen können wir die entsprechende Geschäftslogik aufrufen, wenn sich der Entitätsstatus ändert. Dadurch wird sichergestellt, dass unabhängig davon, wie sich der Entitätsstatus ändert, die Geschäftslogik aufgerufen wird. Es hält Ihre Architektur und auch Ihre Geschäftslogik an einem Ort.
Mit dem obigen Beispiel können wir eine Geschäftslogikmethode namens validateName aufrufen, wenn das Namensfeld mit REST geändert wird:
Mit einem solchen Tool müssen Sie lediglich Ihre Geschäftslogikmethoden entsprechend mit Anmerkungen versehen.
quelle
Sie sollten das Domänenmodell nicht ressourcenorientiert verfügbar machen. Sie sollten die Anwendung ressourcenorientiert verfügbar machen.
Überhaupt nicht - senden Sie die Befehle an Anwendungsressourcen, die mit dem Domänenmodell verbunden sind.
Ja, obwohl es einen etwas anderen Weg gibt, dies zu buchstabieren, der die Dinge einfacher machen könnte; Jede Aktualisierungsoperation für eine REST-API ist einem Prozess zugeordnet, der Befehle an ein oder mehrere Aggregate sendet.
Sie jagen hier den falschen Schwanz.
Stellen Sie sich vor: Nehmen Sie REST vollständig aus dem Bild. Stellen Sie sich stattdessen vor, Sie schreiben eine Desktop-Oberfläche für diese Anwendung. Stellen wir uns weiter vor, dass Sie wirklich gute Designanforderungen haben und eine aufgabenbasierte Benutzeroberfläche implementieren. So erhält der Benutzer eine minimalistische Oberfläche, die perfekt auf die Aufgabe abgestimmt ist, die er gerade bearbeitet. Der Benutzer gibt einige Eingaben ein und drückt dann auf "VERB!" Taste.
Was passiert jetzt? Aus Sicht des Benutzers ist dies eine einzelne atomare Aufgabe. Aus der Sicht des domainModel handelt es sich um eine Reihe von Befehlen, die von Aggregaten ausgeführt werden, wobei jeder Befehl in einer separaten Transaktion ausgeführt wird. Die sind völlig inkompatibel! Wir brauchen etwas in der Mitte, um die Lücke zu schließen!
Das Etwas ist "die Anwendung".
Auf dem Happy-Pfad empfängt die Anwendung einige DTOs und analysiert dieses Objekt, um eine Nachricht zu erhalten, die sie versteht, und verwendet die Daten in der Nachricht, um wohlgeformte Befehle für ein oder mehrere Aggregate zu erstellen. Die Anwendung stellt sicher, dass alle Befehle, die an die Aggregate gesendet werden, korrekt sind (dies ist die Antikorruptionsschicht bei der Arbeit), lädt die Aggregate und speichert die Aggregate, wenn die Transaktion erfolgreich abgeschlossen wurde. Das Aggregat entscheidet für sich, ob der Befehl in Anbetracht seines aktuellen Status gültig ist.
Mögliche Ergebnisse: Die Befehle werden alle erfolgreich ausgeführt. Die Anti-Korruptions-Ebene weist die Meldung zurück. Einige der Befehle werden erfolgreich ausgeführt. Dann beklagt sich eines der Aggregate und es besteht die Möglichkeit, dass Sie Abhilfemaßnahmen ergreifen.
Stellen Sie sich vor, Sie haben diese Anwendung erstellt. Wie interagiert man auf RESTvolle Weise damit?
Accepted (Akzeptiert) ist das übliche Cop-out, wenn die Anwendung die Verarbeitung einer Nachricht bis nach der Antwort an den Client zurückstellt. Dies wird normalerweise beim Akzeptieren eines asynchronen Befehls verwendet. Aber es funktioniert auch gut für diesen Fall, in dem eine Operation, die atomar sein soll, eine Minderung erfordert.
In dieser Redewendung stellt die Ressource die Aufgabe selbst dar. Sie starten eine neue Instanz der Aufgabe, indem Sie die entsprechende Darstellung an die Aufgabenressource senden. Diese Ressource ist mit der Anwendung verbunden und leitet Sie zum nächsten Anwendungsstatus weiter.
In ddd möchten Sie so ziemlich jedes Mal, wenn Sie mehrere Befehle koordinieren, in einem Prozess (auch bekannt als Geschäftsprozess oder Saga) denken.
Es gibt eine ähnliche konzeptionelle Abweichung im Lesemodell. Betrachten Sie erneut die aufgabenbasierte Schnittstelle. Wenn für die Aufgabe mehrere Aggregate geändert werden müssen, enthält die Benutzeroberfläche zur Vorbereitung der Aufgabe wahrscheinlich Daten aus einer Reihe von Aggregaten. Wenn Ihr Ressourcenschema 1: 1 mit Aggregaten ist, wird es schwierig sein, dies zu arrangieren. Stellen Sie stattdessen eine Ressource bereit, die eine Darstellung der Daten aus mehreren Aggregaten zusammen mit einem Hypermedien-Steuerelement zurückgibt, das die Beziehung "Startaufgabe" zum Aufgabenendpunkt wie oben beschrieben abbildet.
Siehe auch: REST in Practice von Jim Webber.
quelle