2012-01-29 10 views
22

Imagine una aplicación web que almacena algunos recursos de datos con una identificación que almacena tres archivos adjuntos (por ejemplo, pdf) por dato.RESTful actualización atómica de múltiples recursos?

El esquema URL es

data/{id}/attachment1 
data/{id}/attachment2 
data/{id}/attachment3 

Existe una API REST para los archivos adjuntos que proporcionan GET/PUT/DELETE operaciones de ejecución de las operaciones CRUD en el lado del servidor.

dejando que el ID sea 123, me gustaría para llevar a cabo una operación en la

  • attachment1 se sustituye por un nuevo accesorio (de manera que GET file/123/attachment1 devuelve el nuevo adjunto) se suprime
  • attachment2 (de tal manera que que GET file/123/attachment2 devuelve 404)
  • attachment3 no se modifica.

La actualización debe ser atómica - la actualización completa se lleva a cabo por el servidor o nada en absoluto.

La aplicación de un simple PUT file/123/attachment1 y DELETE file/123/attachment2 no es atómica, ya que el cliente podría bloquearse después del PUT y el servidor no tiene ninguna pista de que debería realizar una reversión en este caso.

Entonces, ¿cómo implemento la operación de manera RESTful?

he pensado en dos soluciones, pero ambos no parecen estar al 100% REST:

  • el uso del parche (se podría poner, pero PARCHE refleja mejor la semántica de una actualización parcial) con varias piezas/form-data on data/123: multipart/form-data es una secuencia de entidades que consiste en una nueva "aplicación/pdf" asociada con el campo "attachment1" y algo que representaría un valor nulo para indicar eliminación de attachment2.

Si bien esto garantiza la atomicidad, dudo que esto sea RESTful ya que sobrecargo el método PATCH usando diferentes listas de parámetros, lo que viola la restricción uniforme de la interfaz.

  • Utilice un recurso que represente una transacción. Podría PUBLICAR el ID de datos 123 en una transacción-URL que crearía un recurso de transacción que representa una copia del estado actual del recurso de datos almacenado en el servidor, p. transacción/datos/123. Ahora puedo llamar a PUT y DELETE en los archivos adjuntos de este recurso temporal (por ejemplo, DELETE transaction/data/123/attachment2) y comunicar la confirmación de esta versión del recurso al servidor a través de un PUT en transaction/data/123. Esto garantiza la atomicidad mientras que tiene que implementar lógica adicional del lado del servidor para tratar con varios clientes cambiando el mismo recurso y clientes bloqueados que nunca se han comprometido.

Si bien esto parece ser coherente con REST, parece violar el inconveniente de la apatridia.El estado del recurso transaccional no es estado de servicio sino estado de aplicación, ya que cada recurso transaccional está asociado con un único cliente.

Estoy algo atrapado aquí, así que cualquier idea sería útil, ¡gracias!

+2

El segundo enfoque tiene la ventaja de proporcionar un buen historial de cambios en los datos y puede permitirle saltear algunos registros. – Jasper

+0

@mtsz Estoy luchando con este problema ahora mismo. Me gusta la respuesta que seleccionó a continuación, pero parece mucho trabajo crear un recurso de transacción con una vida corta y temporal. ¿Crees que sería malo darle a la transacción atómica un nombre como "switcheroo" y simplemente crear un servicio web específico que realice esa transacción? por ejemplo, POST/doSwitcheroo con un cuerpo de {archivo Id: 123} .... Este servicio tendría la lógica para realizar atómicamente las acciones que describiste arriba en el archivo con Id. 123 –

Respuesta

15

Desea utilizar la segunda opción, la opción de transacción.

lo que se pierde es la creación de la transacción:

POST /transaction 

HTTP/1.1 301 Moved Permanently 
Location: /transaction/1234 

Ahora usted tienen un recurso transacción que es un ciudadano de primera clase. Puede agregarlo, eliminarlo, consultar para ver su contenido actual y, finalmente, confirmarlo o eliminar (es decir, deshacer) la transacción.

Mientras la transacción está en progreso, es solo otro recurso. No hay estado del cliente aquí. Cualquiera puede agregar a esta transacción.

Cuando todo está hecho, el servidor aplica los cambios de una vez usando algún mecanismo interno de transacción que está fuera del alcance aquí.

Puede capturar cosas como Etags y encabezados if-modified en las sub acciones de transacción para que cuando se apliquen todos, sepa que algo no cambió a sus espaldas.

+0

Suena razonable. Probablemente la noción de "estado de aplicación" es un poco exagerada en este caso y este "ciudadano de primera clase" es un estado de servicio normal. [Richardson & Ruby] (http://shop.oreilly.com/product/9780596529260.do) promueven este tipo de solución, aunque se aplica en un escenario con varios servidores. Me pregunto, ¿qué haría Roy Fielding? :) – mtsz

+6

Puede ver la transacción de la misma manera que puede mirar un carrito de compras. En un carro de compras, un cliente construye su transacción a lo largo del tiempo, y luego simplemente realiza el proceso de finalización de la compra que finalmente termina con ellos "confirmando" o "cancelando" el pedido. Cuando lo miras en esos términos, con ese vocabulario, tiene más sentido. Pero a 10,000 pies, básicamente son el mismo problema. "Aquí hay una lista de todas las cosas que quiero hacer, ¡ahora, VAYA!" El hecho de que un carrito de compras esté vinculado a un usuario específico es un problema de seguridad/identidad, no un problema de "estado" frente a "apatridia". –

+0

Implementé esta solución que funciona bien. Usted obtiene el beneficio de resolver "editar carreras" sin perder datos de forma gratuita, lo que no es posible con la "lista" de la solución. – mtsz

5

Pregunta muy interesante. Un profesor CS en la Universidad de Lugano (Suiza) escribió unas diapositivas sobre esta situación:

http://www.slideshare.net/cesare.pautasso/atomic-transactions-for-the-rest-of-us

Sin embargo no estoy realmente seguro de que la solución que ofrecen es totalmente REST, ya que en realidad no parece sin estado en el lado del servidor.

Siendo honesto, ya que la transacción en sí está compuesta por varios estados, no creo que pueda haber una solución totalmente RESTful para este problema.

+1

Desnaudé su papel (Hacia transacciones atómicas distribuidas sobre RESTful Servicios). Proponen un protocolo Try-Cancel/Confirm con un recurso de coordinador que finalmente confirma los cambios en todos los recursos participantes (o inicia la recuperación del estado anterior). Los recursos son servidores distribuidos, por lo que el alcance es ligeramente mayor que el mío. Sin embargo, este protocolo TCC debería ser bastante similar a la segunda solución mencionada anteriormente. Al menos, resuelven conceptualmente el problema de estado de la aplicación convirtiéndolo en estado de servicio adjuntando medios para recuperar el estado anterior. Quizá ese sea el camino. – mtsz

0

Asumiendo que su URIs son jerárquicas:

PUT data/{id} 
[attachment2,attachment3] 

Parte del problema es que attachment1/2/3 es una terrible identificador. Un índice nunca debe ser parte de sus URI.

+0

Los identificadores son arbitrarios para hacer que el escenario sea más genérico. Imagine un escenario concreto en el que tenga blobs de datos con el modelo esperado, la descripción, la imagen, etc. adjuntos. No veo cómo su solución resuelve el problema de una actualización consistente de múltiples recursos adjuntos, ya que los PUT son secuenciales. – mtsz

+0

Piensa en tu lista de archivos adjuntos como recurso. Estás PONIENDO los contenidos y el orden. 1 operación. – noah

+0

Por lo tanto, si entiendo correctamente, opta por utilizar un tipo de medio modelando una secuencia, como multipart/form-data o un tipo personalizado. Dado que mi pregunta original contenía esta solución, sería interesante leer cómo se justifica esta solución. – mtsz

0

No tengo experiencia, pero tengo una idea para una solución ya que estoy enfrentando exactamente este problema en desarrollo.

En primer lugar, utilizo la analogía de mí (cliente) enviando un mensaje a Fred1 en una casa (servidor con recursos) que quiero que apague la luz (cambie el estado de parte de un recurso) y encienda el hervidor (cambiar el estado de otra parte del recurso). Después de apagar el interruptor de la luz, Fred, lamentablemente, tiene un ataque al corazón.

Ahora no he recibido nada de Fred para decir si hizo o no lo que le pedí. Fred es reemplazado por otro Fred. El mensaje que envié no ha recibido respuesta. La única forma en que puedo proceder es preguntarle a Fred2 si el interruptor de la luz está apagado y la tetera encendida (el recurso está en el estado que esperaría después de que le pidiera que hiciera cosas por mí). Este es un desafortunado estado de cosas (error) y se suma a mi carga de trabajo, pero ahora puedo proceder sobre la base de que sé lo que hizo Fred1 antes de su ataque al corazón. Puedo volver al tablero de dibujo (informar al usuario que algo salió mal y tenemos que volver a hacerlo) o realizar los cambios que completarían mi pedido si eso sigue siendo relevante (encienda el hervidor).

Este es el comienzo de cómo lo haría, obviamente hay un ámbito de preocupación, pero si ya he definido mi alcance (solo estoy interesado en el interruptor de la luz y el hervidor) entonces debería tener suficiente información (conociendo el estado del interruptor de la luz y el hervidor) para darle un nuevo comando a Fred2 sin volver al usuario para recibir instrucciones.

¿Cómo te suena?

+0

Interesante, pero problemático: cuando un Fred sufre un ataque cardíaco después de apagar la luz, tiene un estado inconsistente.Será inconsistente siempre que verifique el estado usando su cliente y envíe otro mensaje al próximo Fred (que con suerte no morirá). ¿Qué pasará si tu cliente muere justo después de la muerte de Fred? Entonces el estado deseado se pierde para siempre. Otros clientes se enfrentarán con el estado incoherente actual que puede conducir a problemas graves ... – mtsz

+0

De hecho, es interesante :) ¿Qué pasa con las bases de datos? ¿Cómo lo hacen? Una base de datos tiene que ser atómica internamente, así que tal vez ese sea otro buen lugar para buscar. Estoy ejecutando PostgreSQL así que verificará la documentación. Aquí hay un enlace a PostgreSQL atomic: http://www.postgresql.org/files/developer/transactions.pdf – tentimes

+0

Puede adoptar la noción de una sesión de base de datos (por ejemplo, sesión sql): todas las modificaciones que aplique dentro de su sesión son no realizado, hasta que usted comprometa su sesión. Cuando ocurre algo malo con una de las modificaciones, la sesión se retrotrae y se preserva el estado consistente anterior. Entonces, cuando Fred muere después de encender el interruptor de la luz, arroja una HeartFailureException que queda atrapada, lo que provoca una reversión de la sesión y la luz se enciende de nuevo;) – mtsz

Cuestiones relacionadas