Wie repräsentieren Sie eine bidirektionale Synchronisierung in einer REST-API am besten?

23

Angenommen, auf einem System befindet sich eine Webanwendung mit einer Ressource und ein Verweis auf eine Remoteanwendung mit einer anderen ähnlichen Ressource. Wie stellen Sie eine bidirektionale Synchronisierungsaktion dar, mit der die "lokale" Ressource mit der "Remoteressource" synchronisiert wird?

Beispiel:

Ich habe eine API, die eine Aufgabenliste darstellt.

GET / POST / PUT / DELETE / Aufgaben / usw.

Diese API kann auf entfernte TODO-Dienste verweisen.

GET / POST / PUT / DELETE / todo_services / usw.

Ich kann Aufgaben vom Remote-Service über meine API als Proxy über bearbeiten

GET / POST / PUT / DELETE / todo_services / abc123 / usw.

Ich möchte die Möglichkeit haben, eine bidirektionale Synchronisierung zwischen einer lokalen Gruppe von Aufgaben und der Remote-Gruppe von TODOS durchzuführen.

In gewisser Weise könnte man das tun

POST / todo_services / abc123 / sync /

Aber gibt es in der Idee "Verben sind schlecht" eine bessere Möglichkeit, diese Aktion darzustellen?

Edward M Smith
quelle
4
Ich denke, dass ein gutes API-Design absolut von einem sehr konkreten Verständnis dessen abhängt, was Sie unter Synchronisierung verstehen . Die "Synchronisation" zweier Datenquellen ist normalerweise ein sehr komplexes Problem, das sehr einfach zu vereinfachen ist, dessen Auswirkungen sich jedoch nur schwer durchdenken lassen. Machen Sie es eine "bidirektionale" Synchronisation, und plötzlich ist die Schwierigkeit viel höher. Denken Sie zunächst über die sehr schwierigen Fragen nach, die sich stellen.
Adam Crossland
Richtig - vorausgesetzt, der Synchronisierungsalgorithmus ist in der API auf Codeebene entworfen und funktionsfähig - wie kann ich dies über REST offenlegen? Die Synchronisierung in eine Richtung scheint viel einfacher auszudrücken zu sein: I GET /todo/1/und POSTIt /todo_services/abc123/ To. Aber die 2-Wege-Methode - Ich nehme keinen Datensatz und lege ihn in eine Ressource um. Die von mir ausgeführte Aktion führt tatsächlich zu einer möglichen Änderung von zwei Ressourcen. Ich schätze, ich könnte darauf zurückgreifen, dass "Syncronizations" selbst Ressourcen sind POST /todo_synchronizations/ {"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now"}
Edward M Smith
Wir haben immer noch eine Karren-vor-dem-Pferd-Frage. Mein Punkt war, dass Sie nicht davon ausgehen können, dass die Synchronisierung einfach funktioniert und die API entwerfen. Das Design der API wird von zahlreichen Überlegungen zur genauen Funktionsweise des Synchronisationsalgorithmus bestimmt.
Adam Crossland
Das kann nützliche Ergebnisse liefern: GET /todo_synchronizations/1 =>{"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now","ran_at":"datetime","result":"success"}
Edward M Smith
2
Ich bin mit @Adam einverstanden. Wissen Sie, wie Sie Ihre Synchronisierung implementieren werden? Wie gehst du mit Veränderungen um? Haben Sie einfach zwei Sätze von Elementen, die Sie synchronisieren möchten, oder haben Sie ein Protokoll der Aktionen, die dazu geführt haben, dass die beiden Sätze seit der letzten Synchronisierung auseinander gegangen sind? Der Grund, den ich frage, ist, dass das Erkennen von Hinzufügungen und Löschungen (unabhängig von REST) ​​schwierig sein kann. Wenn Sie ein Objekt serverseitig und nicht clientseitig haben, müssen Sie sich fragen: "Hat der Client es gelöscht oder hat der Server es erstellt?" Nur wenn Sie genau wissen, wie sich die "Ressource" verhält, können Sie sie in REST genau darstellen.
Raymond Saltrelli

Antworten:

17

Wo und was sind die Ressourcen?

Bei REST geht es darum, Ressourcen auf zustandslose, erkennbare Weise anzusprechen. Es muss weder über HTTP implementiert werden, noch muss es sich auf JSON oder XML stützen. Es wird jedoch dringend empfohlen, ein Hypermedia-Datenformat zu verwenden (siehe das HATEOAS- Prinzip), da Links und IDs wünschenswert sind.

Die Frage lautet also: Wie denkt man über Synchronisation in Bezug auf Ressourcen?

Was ist bidirektionale Synchronisierung? **

Bei der bidirektionalen Synchronisierung werden die in einem Knotendiagramm vorhandenen Ressourcen aktualisiert, sodass am Ende des Prozesses alle Knoten ihre Ressourcen gemäß den Regeln für diese Ressourcen aktualisiert haben. In der Regel bedeutet dies, dass auf allen Knoten die aktuellste Version der Ressourcen im Diagramm vorhanden ist. Im einfachsten Fall besteht der Graph aus zwei Knoten: lokal und entfernt. Lokal initiiert die Synchronisierung.

Die Schlüsselressource, die angesprochen werden muss, ist also ein Transaktionsprotokoll. Daher könnte ein Synchronisierungsprozess für die Auflistung "items" unter HTTP folgendermaßen aussehen:

Schritt 1 - Lokal ruft das Transaktionsprotokoll ab

Lokal: GET /remotehost/items/transactions?earliest=2000-01-01T12:34:56.789Z

Remote: 200 OK, wobei der Body ein Transaktionsprotokoll enthält, das ähnliche Felder enthält.

  • itemId - eine UUID zur Bereitstellung eines gemeinsam genutzten Primärschlüssels

  • updatedAt - Zeitstempel, um einen koordinierten Zeitpunkt für die letzte Aktualisierung der Daten anzugeben (vorausgesetzt, ein Änderungsverlauf ist nicht erforderlich)

  • fingerprint- Ein SHA1-Hash des Dateninhalts zum schnellen Vergleich, wenn updateAteinige Sekunden vergangen sind

  • itemURI - eine vollständige URI für den Artikel, um ihn später abrufen zu können

Schritt 2 - Local vergleicht das Remote-Transaktionsprotokoll mit seinem eigenen

Dies ist die Anwendung der Geschäftsregeln für die Synchronisierung. In der Regel itemIdwird die lokale Ressource identifiziert und anschließend der Fingerabdruck verglichen. Wenn es einen Unterschied gibt, wird ein Vergleich von durchgeführt updatedAt. Wenn diese zu nah am Anruf sind, muss eine Entscheidung getroffen werden, basierend auf dem anderen Knoten zu ziehen (vielleicht ist es wichtiger) oder auf den anderen Knoten zu pushen (dieser Knoten ist wichtiger). Wenn die Remote-Ressource nicht lokal vorhanden ist, wird ein Push-Eintrag vorgenommen (dieser enthält die tatsächlichen Daten zum Einfügen / Aktualisieren). Alle lokalen Ressourcen, die nicht im Remote-Transaktionsprotokoll vorhanden sind, werden als unverändert angenommen.

Die Pull-Anforderungen werden an den Remote-Knoten gesendet, sodass die Daten lokal über den vorhanden sind itemURI. Sie werden erst später lokal angewendet.

Schritt 3 - Lokales Synchronisationstransaktionsprotokoll an Remote senden

Lokal: PUT /remotehost/items/transactions mit Hauptteil, der das lokale Synchronisierungstransaktionsprotokoll enthält.

Der entfernte Knoten kann diese synchron verarbeiten (wenn es klein und schnell) oder asynchron (man denke an 202 AKZEPTIERT ) , wenn es wahrscheinlich ist , eine Menge Aufwand entstehen. Unter der Annahme einer synchronen Operation lautet das Ergebnis je nach Erfolg oder Misserfolg entweder 200 OK oder 409 CONFLICT . Bei einem 409 CONFLICT muss der Prozess erneut gestartet werden, da auf dem Remote-Knoten ein optimistischer Sperrfehler aufgetreten ist (jemand hat die Daten während der Synchronisierung geändert). Die Fernaktualisierungen werden unter ihrer eigenen Anwendungstransaktion verarbeitet.

Schritt 4 - Lokal aktualisieren

Die in Schritt 2 abgerufenen Daten werden lokal im Rahmen einer Anwendungstransaktion angewendet.

Das oben Genannte ist zwar nicht perfekt (es gibt mehrere Situationen, in denen lokale und entfernte Geräte Probleme verursachen können und das Abrufen von Daten von lokalen Geräten wahrscheinlich effizienter ist als das Einfügen in einen großen PUT), es zeigt jedoch, wie REST während eines Bi- Richtungssynchronisationsprozess.

Gary Rowe
quelle
6

Ich würde einen Synchronisierungsvorgang als Ressource betrachten, auf die zugegriffen werden kann (GET) oder die erstellt werden kann (POST). In diesem Sinne könnte die API-URL sein:

/todo_services/abc123/synchronization

(Nennen wir es "Synchronisation", nicht "Synchronisation", um deutlich zu machen, dass es kein Verb ist.)

Dann mach:

POST /todo_services/abc123/synchronization

Eine Synchronisation einleiten. Da eine Synchronisierungsoperation eine Ressource ist, kann dieser Aufruf möglicherweise eine ID zurückgeben, mit der der Status der Operation überprüft werden kann:

GET /todo_services/abc123/synchronization?id=12345
laurent
quelle
3
Diese einfache Antwort ist DIE Antwort.
Verwandle
5

Das ist ein schweres Problem. Ich glaube nicht, dass REST eine angemessene Ebene ist, um die Synchronisierung zu implementieren. Eine robuste Synchronisierung müsste im Wesentlichen eine verteilte Transaktion sein. REST ist nicht das Werkzeug für diesen Job.

(Annahme: Durch "Synchronisieren" wird impliziert, dass sich eine Ressource jederzeit unabhängig von der anderen ändern kann und Sie sie neu ausrichten möchten, ohne dass Aktualisierungen verloren gehen.)

Sie können erwägen, einen zum "Master" und einen zum "Slave" zu machen, damit Sie den Slave in regelmäßigen Abständen mit Daten vom Master souverän überlasten können.

Sie können auch das Microsoft Sync Framework in Betracht ziehen, wenn Sie das unabhängige Ändern von Datenspeichern unbedingt unterstützen müssen. Dies würde nicht über REST funktionieren, sondern hinter den Kulissen.

Codierungslautstärke
quelle
5
+1 für "schweres Problem". Die bidirektionale Synchronisierung ist eines der Dinge, bei denen Sie nicht erkennen, wie schwierig es ist, bis Sie tief im Schlamm sind.
Dan Ray
2

Apache CouchDB ist eine Datenbank, die auf REST, HTTP und JSON basiert. Entwickler führen grundlegende CRUD-Operationen über HTTP durch. Es bietet auch einen Replikationsmechanismus, der Peer-to-Peer ist und nur HTTP-Methoden verwendet.

Um diese Replikation bereitzustellen, muss CouchDB über einige CouchDB-spezifische Konventionen verfügen. Keines von diesen ist gegen REST. Es versieht jedes Dokument (das eine REST-Ressource in einer Datenbank ist) mit einer Revisionsnummer . Dies ist Teil der JSON-Darstellung dieses Dokuments, befindet sich jedoch auch im ETag-HTTP-Header. Jede Datenbank verfügt auch über eine Sequenznummer, mit der Änderungen an der gesamten Datenbank verfolgt werden können.

Bei der Konfliktlösung stellen sie einfach fest, dass ein Dokument in Konflikt steht, und behalten die in Konflikt stehenden Versionen bei. Überlassen Sie es den Entwicklern, die die Datenbank verwenden, um einen Konfliktlösungsalgorithmus bereitzustellen.

Sie können entweder CouchDB als REST-API verwenden, mit der Sie sofort synchronisiert werden können, oder sich die Replikation ansehen, um einen Ausgangspunkt für die Erstellung Ihres eigenen Algorithmus zu finden.

David V
quelle
Ich liebe CouchDB und es ist der Nachfolger von CouchBase + SyncGateway. +1
Leonid Usov
-1

Sie können das Problem "Verben sind schlecht" durch einfaches Umbenennen lösen. Verwenden Sie "Updates" anstelle von "Synchronisieren".

Der Synchronisierungsprozess sendet tatsächlich eine Liste der lokalen Aktualisierungen, die seit der letzten Synchronisierung vorgenommen wurden, und empfängt gleichzeitig eine Liste der auf dem Server vorgenommenen Aktualisierungen.

Tom Clarkson
quelle