Ich bin auf Event Sourcing Design gestoßen und möchte es in einer Anwendung verwenden, in der ein REST-Client benötigt wird (RESTful, um genau zu sein). Ich kann diese jedoch nicht miteinander verbinden, da REST ziemlich CRUD-artig ist und das Beschaffen von Ereignissen aufgabenbasiert ist. Ich habe mich gefragt, wie Sie die Erstellung von Befehlen basierend auf Anforderungen an den REST-Server gestalten können. Betrachten Sie dieses Beispiel:
Mit REST können Sie der Ressource Datei einen neuen Status zuweisen. In einer Anfrage können Sie einen neuen Dateinamen senden, den übergeordneten Ordner und / oder den Eigentümer der Datei ändern und so weiter.
Wie baue ich den Server auf, damit ich Event-Sourcing verwenden kann? Ich habe über diese Möglichkeiten nachgedacht:
Bestimmen Sie auf dem Server , welche Felder geändert wurden , und erstellen Sie entsprechende Befehle (
RenameFileCommand
,MoveFileCommand
,ChangeOwnerCommand
, ...) und den Versand individuell diese. In dieser Konfiguration kann es jedoch vorkommen, dass jeder Befehl fehlschlägt und andere nicht mehr zur Transaktion und damit nicht mehr zu einer "atomaren" Änderung der Ressource führen.Versand nur ein Befehl (
UpdateFileCommand
) und in den Befehlshandler, genauer gesagt im Aggregate, festzustellen , welche Felder geändert wurden und einzelne Ereignisse senden statt (FileRenamedEvent
,FileMovedEvent
,OwnerChangedEvent
, ...)Dieses hier mag ich überhaupt nicht: In der Anfrage an den Server würde ich in den Headern angeben, welcher Befehl verwendet werden soll, da die Benutzeroberfläche immer noch aufgabenbasiert ist (aber die Kommunikation erfolgt über REST). Bei jeder anderen Verwendung der REST-Kommunikation (z. B. in externen Apps) schlägt dies jedoch fehl, da diese nicht verpflichtet sind, nur das eine Feld in einer Anforderung zu ändern. Außerdem bringe ich eine ziemlich große Kopplung in das UI-, REST- und ES-basierte Backend.
Welches würden Sie bevorzugen oder gibt es eine bessere Möglichkeit, damit umzugehen?
Randnotiz: App in Java und Axon Framework für Event-Sourcing geschrieben.
quelle
Antworten:
Ich denke, dass Sie möglicherweise einen Benutzerprozess haben, bei dem die Implementierung nicht übereinstimmt.
Erstens: Will ein Benutzer aufrichtig mehrere Änderungen gleichzeitig an einer Datei vornehmen? Eine Umbenennung (die eine Änderung des Pfads beinhalten kann oder nicht?), Eine Änderung des Eigentums und möglicherweise eine Änderung des Dateiinhalts (aus Gründen des Arguments) scheinen separate Aktionen zu sein.
Nehmen wir den Fall, in dem die Antwort "Ja" lautet - Ihre Benutzer möchten diese Änderungen wirklich gleichzeitig vornehmen.
In diesem Fall würde ich empfehlen , gegen jede Implementierung , die mehrere Ereignisse sendet -
RenameFileCommand
,MoveFileCommand
,ChangeOwnerCommand
- diese zu repräsentieren einzelne Benutzer Absicht.Warum? Weil Ereignisse fehlschlagen können. Möglicherweise ist es äußerst selten, aber Ihr Benutzer hat eine Operation übermittelt, die atomar aussah. Wenn eines der nachfolgenden Ereignisse fehlschlägt, ist Ihr Anwendungsstatus jetzt ungültig.
Sie laden auch Rennrisiken für eine Ressource ein, die eindeutig von jedem der Event-Handler geteilt wird. Sie müssen "ChangeOwnerCommand" so schreiben, dass der Dateiname und der Dateipfad keine Rolle spielen, da sie zum Zeitpunkt des Befehlsempfangs möglicherweise veraltet sind.
Bei der Implementierung eines nicht ereignisgesteuerten, erholsamen Systems mit dem Verschieben und Umbenennen von Dateien ziehe ich es vor, die Konsistenz mithilfe eines eTag-Systems sicherzustellen. Stellen Sie sicher, dass die Version der bearbeiteten Ressource der Version entspricht, die der Benutzer zuletzt abgerufen hat, und schlagen Sie fehl, wenn dies der Fall ist wurde seitdem geändert. Wenn Sie jedoch mehrere Befehle für diesen Einzelbenutzer-Vorgang senden, müssen Sie Ihre Ressourcenversion nach jedem Befehl erhöhen. Sie können also nicht feststellen, dass die vom Benutzer bearbeitete Ressource tatsächlich dieselbe Version wie die zuletzt gelesene Ressource ist .
Was ich damit meine, ist - was, wenn jemand anderes fast zur gleichen Zeit eine andere Operation an der Datei ausführt. Die 6 Befehle können in beliebiger Reihenfolge gestapelt werden. Wenn wir nur 2 atomare Befehle hätten, könnte der frühere Befehl erfolgreich sein und der spätere Befehl könnte fehlschlagen "Ressource wurde seit dem letzten Abrufen geändert". Es gibt jedoch keinen Schutz dagegen, wenn die Befehle nicht atomar sind, sodass die Systemkonsistenz verletzt wird.
Interessanterweise gibt es in REST eine Bewegung in Richtung einer ereignisbasierten Architektur mit dem Namen "Rest without PUT", die vom Thoughtworks-Technologieradar im Januar 2015 empfohlen wird . Hier gibt es einen wesentlich längeren Blog über Rest without PUT .
Grundsätzlich ist die Idee, dass POST, PUT, DELETE und GET für kleine Anwendungen in Ordnung sind. Wenn Sie jedoch davon ausgehen müssen, wie put und post und delete am anderen Ende interpretiert werden, führen Sie die Kopplung ein. (z. B. "Wenn ich die mit meinem Bankkonto verknüpfte Ressource lösche, sollte das Konto geschlossen werden"). Die vorgeschlagene Lösung besteht darin, REST auf eine ereignisbasiertere Weise zu behandeln. Das heißt, Sie können die Benutzerabsicht als einzelne Ereignisressource POST.
Der andere Fall ist einfacher. Wenn Ihre Benutzer nicht alle diese Vorgänge gleichzeitig ausführen möchten, lassen Sie sie nicht zu. POST ein Ereignis für jede Benutzerabsicht. Jetzt können Sie die etag-Versionierung für Ihre Ressourcen verwenden.
Wie für die anderen Anwendungen, die eine ganz andere API als Ihre Ressourcen verwenden. Das riecht nach Ärger. Können Sie eine Fassade der alten API über Ihrer RESTful-API erstellen und auf die Fassade verweisen? dh einen Dienst verfügbar machen, der mehrere Aktualisierungen einer Datei nacheinander über den REST-Server ausführt?
Wenn Sie weder die RESTful-Schnittstelle auf der alten Lösung noch eine Fassade der alten Schnittstelle auf der REST-Lösung erstellen und versuchen, beide APIs so zu verwalten, dass sie auf eine gemeinsam genutzte Datenressource verweisen, treten erhebliche Probleme auf.
quelle
Gerade bin ich auf den folgenden Artikel gestoßen, in dem empfohlen wird, die Befehlsnamen in der Anforderung an den Server im Content-Type-Header anzugeben (während 5 Stufen des Medientyps befolgt werden).
In dem Artikel wird erwähnt, dass der RPC-Stil schlecht für REST ist, und es wird empfohlen, den Inhaltstyp zu erweitern, um den Befehlsnamen anzugeben:
Der Artikel ist hier: http://www.infoq.com/articles/rest-api-on-cqrs
Weitere Informationen zu 5 Arten von Medientypen finden Sie hier: http://byterot.blogspot.co.uk/2012/12/5-levels-of-media-type-rest-csds.html
Obwohl sie die Domänenereignisse der REST-API aussetzen, was ich für eine schlechte Praxis halte, gefällt mir die Lösung, da sie kein neues "Protokoll" ausschließlich für CQRS erstellt, sei es das Senden von Befehlsnamen im Hauptteil oder zusätzlich Header und bleibt den RESTful-Prinzipien treu.
quelle