¿Cuál es la mejor forma de representar una sincronización bidireccional en una API REST?

23

Suponiendo un sistema donde hay una aplicación web con un recurso y una referencia a una aplicación remota con otro recurso similar, ¿cómo representa una acción de sincronización bidireccional que sincroniza el recurso 'local' con el recurso 'remoto'?

Ejemplo:

Tengo una API que representa una lista de tareas pendientes.

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

Esa API puede hacer referencia a servicios TODO remotos.

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

Puedo manipular todos desde el servicio remoto a través de mi API como proxy a través de

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

Quiero la capacidad de hacer una sincronización bidireccional entre un conjunto local de todos y el conjunto remoto de TODOS.

De una manera rpc, uno podría hacer

POST / todo_services / abc123 / sync /

Pero, en la idea de "los verbos son malos", ¿hay una mejor manera de representar esta acción?

Edward M Smith
fuente
44
Creo que un buen diseño de API depende absolutamente de una comprensión muy concreta de lo que quiere decir con sincronización. La "sincronización" de dos fuentes de datos suele ser un problema muy complejo que es muy fácil de simplificar en exceso pero muy difícil de pensar en todas sus implicaciones. Haga una sincronización "bidireccional", y de repente la dificultad es mucho mayor. Comience por pensar en las preguntas muy difíciles que surgen.
Adam Crossland
Correcto: suponga que el algoritmo de sincronización está diseñado y es funcional en la API de "nivel de código". ¿Cómo expongo esto a través de REST? La sincronización unidireccional parece mucho más fácil de expresar: I GET /todo/1/y POSTit to /todo_services/abc123/ Pero, la segunda forma: no estoy tomando un conjunto de datos y PONIÉNDOLO a un recurso, la acción que estoy tomando en realidad resulta en la modificación potencial de dos recursos. Supongo que podría recurrir a que las "sincronizaciones de todo" sean recursos en sí mismos POST /todo_synchronizations/ {"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now"}
Edward M Smith
Todavía tenemos un problema de carro antes del caballo. Mi punto era que no puedes asumir que la sincronización solo funciona y diseñar la API. El diseño de la API estará impulsado por numerosas preocupaciones sobre cómo funciona exactamente el algoritmo de sincronización.
Adam Crossland
Eso expone potencialmente resultados útiles: GET /todo_synchronizations/1=>{"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now","ran_at":"datetime","result":"success"}
Edward M Smith
2
Estoy de acuerdo con @Adam. ¿Sabes cómo vas a implementar tu sincronización? ¿Cómo manejas los cambios? ¿Simplemente tiene dos conjuntos de elementos que desea conciliar o tiene un registro de las acciones que causaron la divergencia de los dos conjuntos desde la última sincronización? La razón por la que pregunto es que puede ser complicado detectar adiciones y eliminaciones (independientemente de REST). Si tiene un objeto del lado del servidor y no lo tiene del lado del cliente, debe preguntarse: "¿El cliente lo eliminó o el servidor lo creó?" Solo cuando sepa con precisión cómo se comporta el "recurso" puede representarlo con precisión en REST.
Raymond Saltrelli

Respuestas:

17

¿Dónde y cuáles son los recursos?

REST se trata de abordar los recursos de una manera apátrida y reconocible. No tiene que implementarse a través de HTTP, ni debe depender de JSON o XML, aunque se recomienda encarecidamente que se utilice un formato de datos hipermedia (consulte el principio HATEOAS ) ya que los enlaces y los identificadores son deseables.

Entonces, la pregunta es: ¿cómo se piensa acerca de la sincronización en términos de recursos?

¿Qué es la sincronización bidireccional? **

La sincronización bidireccional es el proceso de actualizar los recursos presentes en un gráfico de nodos para que, al final del proceso, todos los nodos hayan actualizado sus recursos de acuerdo con las reglas que rigen esos recursos. Normalmente, se entiende que todos los nodos tendrían la última versión de los recursos tal como está presente en el gráfico. En el caso más simple, el gráfico consta de dos nodos: local y remoto. Local inicia la sincronización.

Por lo tanto, el recurso clave que debe abordarse es un registro de transacciones y, por lo tanto, un proceso de sincronización podría verse así para la colección de "elementos" en HTTP:

Paso 1: Local recupera el registro de transacciones

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

Remoto: 200 OK con el cuerpo que contiene el registro de transacciones que contiene campos similares a este.

  • itemId - un UUID para proporcionar una clave primaria compartida

  • updatedAt - marca de tiempo para proporcionar un punto coordinado cuando los datos se actualizaron por última vez (suponiendo que no se requiere un historial de revisión)

  • fingerprint- un hash SHA1 del contenido de los datos para una comparación rápida si faltan updateAtunos segundos

  • itemURI - un URI completo para el elemento para permitir la recuperación posterior

Paso 2: Local compara el registro de transacciones remotas con el suyo

Esta es la aplicación de las reglas comerciales de cómo sincronizar. Por lo general, itemIdidentificará el recurso local y luego comparará la huella digital. Si hay una diferencia, se hace una comparación de updatedAt. Si estos están demasiado cerca de la llamada, entonces se deberá tomar una decisión para extraer en función del otro nodo (tal vez sea más importante), o para empujar al otro nodo (este nodo es más importante). Si el recurso remoto no está presente localmente, se realiza una entrada de inserción (esta contiene los datos reales para insertar / actualizar). Se supone que los recursos locales que no están presentes en el registro de transacciones remotas no se modifican.

Las solicitudes de extracción se realizan en el nodo remoto para que los datos existan localmente utilizando itemURI. No se aplican localmente hasta más tarde.

Paso 3: empuje el registro de transacciones de sincronización local al control remoto

Local: PUT /remotehost/items/transactions con cuerpo que contiene el registro de transacciones de sincronización local.

El nodo remoto podría procesar esto sincrónicamente (si es pequeño y rápido) o asincrónicamente (piense 202 ACEPTADO ) si es probable que incurra en una sobrecarga. Asumiendo una operación sincrónica, el resultado será 200 OK o 409 CONFLICT dependiendo del éxito o el fracaso. En el caso de un CONFLICTO 409 , entonces el proceso debe iniciarse nuevamente ya que ha habido un fallo de bloqueo optimista en el nodo remoto (alguien cambió los datos durante la sincronización). Las actualizaciones remotas se procesan bajo su propia transacción de aplicación.

Paso 4 - Actualiza localmente

Los datos extraídos en el Paso 2 se aplican localmente en una transacción de aplicación.

Si bien lo anterior no es perfecto (hay varias situaciones en las que lo local y lo remoto pueden tener problemas y tener datos remotos de extracción local es probablemente más eficiente que meterlo en un gran PUT), demuestra cómo REST puede usarse durante un bi- proceso de sincronización direccional.

Gary Rowe
fuente
6

Consideraría una operación de sincronización como un recurso al que se puede acceder (GET) o crear (POST). Con eso en mente, la URL de la API podría ser:

/todo_services/abc123/synchronization

(Llamándolo "sincronización", no "sincronización" para dejar en claro que no es un verbo)

Entonces hazlo:

POST /todo_services/abc123/synchronization

Para iniciar una sincronización. Dado que una operación de sincronización es un recurso, esta llamada podría devolver una identificación que luego puede usarse para verificar el estado de la operación:

GET /todo_services/abc123/synchronization?id=12345
Laurent
fuente
3
Esta respuesta simple es LA respuesta. Convierte tus verbos en sustantivos y
sigue
5

Es este un problema difícil. No creo que REST sea un nivel apropiado para implementar la sincronización. Una sincronización robusta esencialmente necesitaría ser una transacción distribuida. REST no es la herramienta para ese trabajo.

(Supuesto: al "sincronizar" usted implica que cualquiera de los recursos puede cambiar independientemente del otro en cualquier momento, y desea la capacidad de realinearlos sin perder actualizaciones).

Es posible que desee considerar hacer que uno sea el "maestro" y el otro el "esclavo" para que pueda golpear al esclavo con confianza periódicamente con datos del maestro.

También es posible que desee considerar el Marco de sincronización de Microsoft si realmente necesita admitir el cambio independiente de los almacenes de datos. Esto no funcionaría a través de REST, sino detrás de escena.

codingoutloud
fuente
55
+1 para "problema difícil". La sincronización bidireccional es una de esas cosas que no te das cuenta de lo difícil que es hasta que estás en el lodo.
Dan Ray
2

Apache CouchDB es una base de datos basada en REST, HTTP y JSON. Los desarrolladores realizan operaciones CRUD básicas a través de HTTP. También proporciona un mecanismo de replicación que es de igual a igual utilizando solo métodos HTTP.

Para proporcionar esta replicación, CouchDB necesita tener algunas convenciones específicas de CouchDB. Ninguno de estos se opone a REST. Proporciona a cada documento (que es un recurso REST dentro de una base de datos) un número de revisión . Esto es parte de la representación JSON de ese documento, pero también está en el encabezado HTTP ETag. Cada base de datos también tiene un número de secuencia que permite realizar un seguimiento de los cambios en la base de datos en su conjunto.

Para la resolución de conflictos , simplemente notan que un documento está en conflicto y retienen las versiones en conflicto, dejándolo a los desarrolladores que usan la base de datos para proporcionar un algoritmo de resolución de conflictos.

Puede usar CouchDB como su API REST, que le dará sincronización de fábrica, o echar un vistazo a cómo proporciona la replicación para proporcionar un punto de partida para crear su propio algoritmo.

David V
fuente
Me encanta CouchDB, y es su sucesor CouchBase + SyncGateway. +1
Leonid Usov
-1

Puede resolver el problema de "los verbos son malos" con un cambio de nombre simple: use "actualizaciones" en lugar de "sincronización".

El proceso de sincronización en realidad está enviando una lista de actualizaciones locales realizadas desde la última sincronización y recibiendo una lista de actualizaciones realizadas en el servidor en ese mismo tiempo.

Tom Clarkson
fuente