Comment représenter au mieux une synchronisation bidirectionnelle dans une API REST?

23

En supposant un système où il y a une application Web avec une ressource et une référence à une application distante avec une autre ressource similaire, comment représentez-vous une action de synchronisation bidirectionnelle qui synchronise la ressource `` locale '' avec la ressource `` distante ''?

Exemple:

J'ai une API qui représente une liste de tâches.

GET / POST / PUT / DELETE / todos /, etc.

Cette API peut référencer des services TODO distants.

GET / POST / PUT / DELETE / todo_services /, etc.

Je peux manipuler les todos du service distant via mon API en tant que proxy via

GET / POST / PUT / DELETE / todo_services / abc123 /, etc.

Je veux pouvoir effectuer une synchronisation bidirectionnelle entre un ensemble local de todos et l'ensemble distant de TODOS.

D'une manière rpc, on pourrait faire

POST / todo_services / abc123 / sync /

Mais, dans l'idée "les verbes sont mauvais", existe-t-il une meilleure façon de représenter cette action?

Edward M Smith
la source
4
Je pense qu'une bonne conception d'API dépend absolument d'une compréhension très concrète de ce que vous entendez par synchronisation. La «synchronisation» de deux sources de données est généralement un problème très complexe qui est très facile à simplifier à l'extrême mais très difficile à réfléchir dans toutes ses implications. Faites-en une synchronisation "bidirectionnelle", et soudain la difficulté est beaucoup plus élevée. Commencez par réfléchir aux questions très difficiles qui se posent.
Adam Crossland
À droite - supposons que l'algorithme de synchronisation est conçu et fonctionnel dans l'API "au niveau du code" - comment puis-je l'exposer via REST. La synchronisation à sens unique semble beaucoup plus facile à exprimer: moi GET /todo/1/et POSTcela à /todo_services/abc123/ Mais, la 2 façon - je ne prends pas un ensemble de données et ne le mets PAS dans une ressource, l'action que je prends entraîne en fait la modification potentielle de deux ressources. Je suppose que je pourrais me rabattre sur le fait que les "todo syncronisations" soient ellesPOST /todo_synchronizations/ {"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now"}
Edward M Smith
Nous avons toujours un problème de chariot avant le cheval. Mon point était que vous ne pouvez pas supposer que la synchronisation fonctionne et concevoir l'API. La conception de l'API sera motivée par de nombreuses préoccupations quant au fonctionnement exact de l'algorithme de synchronisation.
Adam Crossland
Cela expose potentiellement des résultats utiles: GET /todo_synchronizations/1=>{"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now","ran_at":"datetime","result":"success"}
Edward M Smith
2
Je suis d'accord avec @Adam. Savez-vous comment vous allez implémenter votre synchronisation? Comment gérez-vous les changements? Avez-vous simplement deux ensembles d'éléments que vous souhaitez rapprocher ou avez-vous un journal des actions qui ont provoqué la divergence des deux ensembles depuis la dernière synchronisation? La raison pour laquelle je pose la question est qu'il peut être difficile de détecter les ajouts et les suppressions (indépendamment de REST). Si vous avez un objet côté serveur et que vous ne l'avez pas côté client, vous devez vous demander: "Le client l'a-t-il supprimé ou le serveur l'a-t-il créé?" Ce n'est que lorsque vous savez exactement comment se comporte la "ressource" que vous pouvez la représenter avec précision dans REST.
Raymond Saltrelli

Réponses:

17

Où et quelles sont les ressources?

REST consiste à adresser les ressources de manière apatride et détectable. Il n'a pas besoin d'être implémenté sur HTTP, ni de s'appuyer sur JSON ou XML, bien qu'il soit fortement recommandé d'utiliser un format de données hypermédia (voir le principe HATEOAS ) car les liens et les identifiants sont souhaitables.

Alors, la question devient: comment penser la synchronisation en termes de ressources?

Qu'est-ce que la synchronisation bidirectionnelle? **

La synchronisation bidirectionnelle est le processus de mise à jour des ressources présentes sur un graphe de nœuds afin qu'à la fin du processus, tous les nœuds aient mis à jour leurs ressources conformément aux règles régissant ces ressources. En règle générale, cela signifie que tous les nœuds disposeraient de la dernière version des ressources telles qu'elles sont présentes dans le graphique. Dans le cas le plus simple, le graphique se compose de deux nœuds: local et distant. Local lance la synchronisation.

Ainsi, la ressource clé qui doit être adressée est un journal des transactions et, par conséquent, un processus de synchronisation peut ressembler à ceci pour la collection "items" sous HTTP:

Étape 1 - Local récupère le journal des transactions

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

À distance: 200 OK avec un corps contenant le journal des transactions contenant des champs similaires à celui-ci.

  • itemId - un UUID pour fournir une clé primaire partagée

  • updatedAt - horodatage pour fournir un point coordonné lors de la dernière mise à jour des données (en supposant qu'aucun historique de révision n'est requis)

  • fingerprint- un hachage SHA1 du contenu des données pour une comparaison rapide en updateAtquelques secondes

  • itemURI - un URI complet à l'élément pour permettre une récupération ultérieure

Étape 2 - Local compare le journal des transactions distant avec le sien

Il s'agit de l'application des règles commerciales de synchronisation. En règle générale, le itemIdidentifie la ressource locale, puis compare l'empreinte digitale. S'il y a une différence, une comparaison updatedAtest effectuée. Si ceux-ci sont trop proches pour être appelés, une décision devra être prise pour tirer en fonction de l'autre nœud (peut-être que c'est plus important), ou pour pousser vers l'autre nœud (ce nœud est plus important). Si la ressource distante n'est pas présente localement, une entrée push est effectuée (elle contient les données réelles pour l'insertion / la mise à jour). Toutes les ressources locales non présentes dans le journal des transactions distant sont supposées être inchangées.

Les demandes d'extraction sont effectuées sur le nœud distant afin que les données existent localement à l'aide de itemURI. Ils ne sont appliqués localement que plus tard.

Étape 3 - Envoyer le journal des transactions de synchronisation locale à distance

Local: PUT /remotehost/items/transactions avec corps contenant le journal des transactions de synchronisation local.

Le nœud distant peut traiter cela de manière synchrone (s'il est petit et rapide) ou asynchrone (pensez 202 ACCEPTÉ ) s'il est susceptible d'entraîner une surcharge importante. En supposant une opération synchrone, le résultat sera soit 200 OK ou 409 CONFLIT selon le succès ou l'échec. Dans le cas d'un 409 CONFLICT , alors le processus doit être redémarré car il y a eu une défaillance de verrouillage optimiste au niveau du nœud distant (quelqu'un a changé les données pendant la synchronisation). Les mises à jour à distance sont traitées dans le cadre de leur propre transaction d'application.

Étape 4 - Mettre à jour localement

Les données extraites à l'étape 2 sont appliquées localement dans le cadre d'une transaction d'application.

Bien que ce qui précède ne soit pas parfait (il existe plusieurs situations où local et distant peuvent avoir des problèmes et avoir des données d'extraction à distance du local est probablement plus efficace que de les mettre dans un gros PUT), il montre comment REST peut être utilisé pendant un bi- processus de synchronisation directionnelle.

Gary Rowe
la source
6

Je considérerais une opération de synchronisation comme une ressource accessible (GET) ou créée (POST). Dans cet esprit, l'URL de l'API pourrait être:

/todo_services/abc123/synchronization

(L'appeler "synchronisation", pas "sync" pour qu'il soit clair que ce n'est pas un verbe)

Alors fais:

POST /todo_services/abc123/synchronization

Pour lancer une synchronisation. Étant donné qu'une opération de synchronisation est une ressource, cet appel peut potentiellement renvoyer un ID qui peut ensuite être utilisé pour vérifier l'état de l'opération:

GET /todo_services/abc123/synchronization?id=12345
laurent
la source
3
Cette réponse simple est LA réponse. Transformez vos verbes en noms et continuez ...
HDave
5

C'est un problème difficile. Je ne pense pas que REST soit un niveau approprié pour implémenter la synchronisation. Une synchronisation robuste devrait essentiellement être une transaction distribuée. REST n'est pas l'outil pour ce travail.

(Hypothèse: par "synchronisation", vous indiquez que l'une ou l'autre ressource peut changer indépendamment de l'autre à tout moment, et vous voulez pouvoir les réaligner sans perdre les mises à jour.)

Vous voudrez peut-être envisager de faire de l'un "maître" et de l'autre "esclave" afin de pouvoir assommer l'esclave périodiquement en toute confiance avec les données du maître.

Vous pouvez également envisager l'utilisation de Microsoft Sync Framework si vous avez absolument besoin de prendre en charge des magasins de données à modification indépendante. Cela ne fonctionnerait pas via REST, mais dans les coulisses.

codage
la source
5
+1 pour "problème difficile". La synchronisation bidirectionnelle est l'une de ces choses que vous ne réalisez pas à quel point c'est difficile jusqu'à ce que vous soyez profondément dans la boue.
Dan Ray
2

Apache CouchDB est une base de données basée sur REST, HTTP et JSON. Les développeurs effectuent des opérations CRUD de base sur HTTP. Il fournit également un mécanisme de réplication qui est peer-to-peer en utilisant uniquement des méthodes HTTP.

Pour fournir cette réplication, CouchDB doit avoir certaines conventions spécifiques à CouchDB. Aucun de ceux-ci n'est opposé au REST. Il fournit à chaque document (c'est-à-dire une ressource REST dans une base de données) un numéro de révision . Cela fait partie de la représentation JSON de ce document, mais se trouve également dans l'en-tête HTTP ETag. Chaque base de données possède également un numéro de séquence qui permet de suivre les modifications apportées à la base de données dans son ensemble.

Pour la résolution des conflits , ils notent simplement qu'un document est en conflit et conservent les versions en conflit, le laissant aux développeurs utilisant la base de données pour fournir un algorithme de résolution des conflits.

Vous pouvez soit utiliser CouchDB en tant qu'API REST, ce qui vous donnera une synchronisation prête à l'emploi, ou jeter un œil à la façon dont il fournit la réplication pour fournir un point de départ pour créer votre propre algorithme.

David V
la source
J'adore CouchDB et son successeur CouchBase + SyncGateway. +1
Leonid Usov
-1

Vous pouvez résoudre le problème "les verbes sont mauvais" avec un simple changement de nom - utilisez "mises à jour" au lieu de "synchronisation".

Le processus de synchronisation envoie en fait la liste des mises à jour locales effectuées depuis la dernière synchronisation et reçoit une liste des mises à jour effectuées sur le serveur en même temps.

Tom Clarkson
la source