Event Sourcing und REST

17

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:

  1. 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.

  2. 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, ...)

  3. 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.

Rothaarige
quelle
Mit Sicherheit nicht der 3.. Ich würde den 1. machen, aber ich muss darüber nachdenken.
Inf3rno
Haben Sie Fragen dazu, ob eine einzelne HTTP-Anforderung mehrere Befehle zur Folge haben kann? Verstehe ich gut Meiner bescheidenen Meinung nach. Dies sollte möglich sein, aber ich habe eine Weile nichts über DDD gelesen, daher muss ich einen Beispielcode zur Implementierung überprüfen.
Inf3rno
Ich habe ein Beispiel gefunden, aber es ist CRUD. github.com/szjani/predaddy-issuetracker-sample/blob/3.0/src/hu/… Ich werde den Autor nach seiner Meinung fragen, er weiß mehr über DDD als ich.
Inf3rno
1
Meins ist, dass Sie mehrere Befehle in einer "Arbeitseinheit" verwenden sollten, wenn Sie sofortige Konsistenz wünschen. Wenn Sie über eine eventuelle Konsistenz sprechen, ergibt die Frage für mich keinen Sinn. Eine andere mögliche Lösung, um einen CompositeCommand zu senden, der die Befehle enthalten kann, die Sie atomar ausführen möchten. Es kann eine einfache Sammlung sein, das einzige, was zählt, ist, dass der Bus richtig damit umgehen kann.
Inf3rno
1
Ihm zufolge sollten Sie versuchen, eine 1: 1-Beziehung zwischen Befehlen und HTTP-Anforderungen zu erreichen. Wenn Sie es nicht können (das ist ein schlechter Geruch), sollten Sie Bulk (ich nannte es Composite) verwenden, um es atomar zu machen.
Inf3rno

Antworten:

11

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.

Perfektionist
quelle
Ich kann die Nichtübereinstimmung sehen, aber durch REST ist es im Prinzip möglich, den Status mehrerer Felder zu aktualisieren, indem der neue Status der Ressource zugewiesen wird (durch eine bestimmte Darstellung). So funktioniert REST per Definition - Übertragung des Repräsentationsstatus. Der Nachteil ist, dass die Absicht des Benutzers dabei verloren geht. Außerdem ist REST fast ein Standard für externe APIs. Ich möchte die App nur so gestalten, dass ich beide verwenden kann. Das Entfernen von PUT ist wie eine Problemumgehung aufgrund von ES. Soweit ich sehen kann, kann ein Aktualisierungsbefehl, der mehrere Ereignisse ausgibt, ausgeführt werden, solange sich der Befehl und die Ereignisbehandlung in einer Transaktion befinden.
Rotschopf
Etag würde ich sowieso gerne machen. Auch Ihr Link zu "längerem Blogpost" ist derselbe wie der erste Link.
Rotschopf
Ich werde darauf nach einiger Überlegung mit weiteren Gedanken zu PUT zurückkommen. Das Entfernen von PUT ist sicherlich nicht nur eine "Problemumgehung für ES". Ich habe den Blog-Link behoben.
Perfektionist
4

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:

Ein gängiger Ansatz ist die Verwendung von Ressourcen im RPC-Stil, z. B. / api / InventoryItem / {id} / rename. Während dies anscheinend die Notwendigkeit willkürlicher Verben beseitigt, widerspricht es der ressourcenorientierten Darstellung von REST. Wir müssen daran erinnern, dass eine Ressource ein Substantiv und ein HTTP-Verb ein Verb / eine Aktion ist und dass selbstbeschreibende Nachrichten (einer der Grundsätze von REST) ​​das Mittel sind, um andere Informations- und Absichtsachsen zu vermitteln. Tatsächlich sollte der Befehl in der Nutzlast der HTTP-Nachricht ausreichen, um eine beliebige Aktion auszudrücken. Sich auf den Hauptteil der Nachricht zu verlassen, ist jedoch mit eigenen Problemen verbunden, da der Hauptteil normalerweise als Datenstrom übertragen wird und der Hauptteil in seiner Gesamtheit gepuffert wird, bevor eine Identifizierung nicht immer möglich oder sinnvoll ist.

PUT /api/InventoryItem/4454c398-2fbb-4215-b986-fb7b54b62ac5 HTTP/1.1
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Content-Type:application/json;domain-model=RenameInventoryItemCommand`

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.

Rothaarige
quelle