2008-09-29 11 views
124

Me pregunto cómo implementaría el siguiente caso de uso en REST. ¿Es posible hacerlo sin comprometer el modelo conceptual?¿Transacciones en REST?

Lea o actualice varios recursos dentro del alcance de una sola transacción. Por ejemplo, transfiera $ 100 de la cuenta bancaria de Bob a la cuenta de John.

Por lo que puedo decir, la única forma de implementar esto es haciendo trampa. Puede PUBLICAR en el recurso asociado con John o Bob y llevar a cabo toda la operación utilizando una sola transacción. En lo que a mí respecta, esto rompe la arquitectura REST porque esencialmente estás haciendo un túnel de una llamada RPC a través de POST en lugar de realmente operar con recursos individuales.

Respuesta

79

Considere un escenario RESTful cesta de la compra. La cesta de la compra es conceptualmente su envoltorio de transacción. De la misma manera que puede agregar varios artículos a una cesta de la compra y luego enviar esa cesta para procesar la orden, puede agregar la entrada de la cuenta de Bob al envoltorio de la transacción y luego la entrada de la cuenta de Bill al envoltorio. Cuando todas las piezas estén en su lugar, puede POST/PUT la envoltura de transacción con todas las piezas componentes.

+0

Obtengo el ejemplo de la cesta de la compra , pero cómo cuando generalizas esto no suena RESTful en absoluto (no estás operando con recursos). No hay un recurso realista relacionado con el banco para envolver a John y Bob. ¡Buena respuesta! Siento que definitivamente te estás moviendo en la dirección correcta. – Gili

+15

¿Por qué TransferMoneyTransaction no sería un recurso bancario viable? –

+1

Pensé que se suponía que un recurso era un objeto de la vida real, no un servicio (es decir, una invocación al método). Si puede inventar recursos que imitan a los servicios, ¿cómo es REST diferente de RPC? – Gili

9

Tendría que lanzar su propio tipo de gestión de tx "identificación de transacción". Por lo que sería de 4 llamadas:

http://service/transaction (some sort of tx request) 
http://service/bankaccount/bob (give tx id) 
http://service/bankaccount/john (give tx id) 
http://service/transaction (request to commit) 

Habría que manejar el almacenamiento de las acciones en una base de datos (si la carga equilibrada) o en la memoria o similares, a continuación, el manejo de cometer, rollback, tiempo de espera.

No es realmente un día RESTful en el parque.

+4

No creo que esta sea una ilustración particularmente buena. Solo necesita dos pasos: Crear transacción (crea una transacción en estado "pendiente") y Confirmar transacción (confirma si no está comprometido y mueve el recurso al estado comprometido o revertido). –

-3

Creo que se puede incluir el bronceado en el URL/recurso:

  1. PUT/transacción para obtener el ID (por ejemplo, "1")
  2. [PUT, GET, POST, lo que sea]/1/account/bob
  3. [PUT, GET, POST, lo que sea]/1/cuenta/factura
  4. BORRAR/transacción con ID 1

Es sólo una idea.

+0

Veo dos problemas con este enfoque: 1) Implica que no puede acceder a un recurso fuera de una transacción (aunque tal vez esto no sea un gran problema). 2) Ninguna de las respuestas hasta ahora ha tocado el hecho de que el servidor ya no es apátrida, aunque sospecho que no se puede hacer nada al respecto. – Gili

+0

Bueno,/1/account/bob y/account/bob son solo dos recursos diferentes. :) Y RE: sin estado, implica que el recurso siempre está disponible y no depende de una solicitud previa. Como solicitó transacciones, sí, ese no es el caso. Pero, nuevamente, querías transacciones. – Till

+1

Si un cliente tiene que ensamblar URI, entonces su API no es RESTful. – aehlke

2

Creo que en este caso es totalmente aceptable romper la teoría pura del REST en esta situación. En cualquier caso, no creo que haya nada realmente en REST que diga que no puede tocar objetos dependientes en casos comerciales que lo requieran.

Realmente creo que no vale la pena los aros adicionales que saltaría para crear un administrador de transacciones personalizado, cuando podría aprovechar la base de datos para hacerlo.

0

En el caso simple (sin recursos distribuidos), podría considerar la transacción como un recurso, donde el acto de crearlo alcanza el objetivo final.

Por lo tanto, para transferir entre <url-base>/account/a y <url-base>/account/b, puede publicar lo siguiente en <url-base>/transfer.

 
<transfer> 
    <from><url-base>/account/a</from> 
    <to><url-base>/account/b</to> 
    <amount>50</amount> 
</transfer> 

Esto crearía un nuevo recurso de transferencia y devolver la nueva URL de la transferencia - por ejemplo <url-base>/transfer/256.

En el momento de la publicación exitosa, entonces, la transacción 'real' se lleva a cabo en el servidor, y el monto se elimina de una cuenta y se agrega a otra.

Esto, sin embargo, no cubre una transacción distribuida (si, digamos 'a' se mantiene en un banco detrás de un servicio, y 'b' se encuentra en otro banco detrás de otro servicio) - aparte de decir " intente sintetizar todas las operaciones de forma que no requieran transacciones distribuidas ".

+2

Si no puede "expresar todas las operaciones de forma que no requieran transacciones distribuidas", entonces realmente lo hace necesita un compromiso en dos fases. La mejor idea que pude encontrar para implementar el compromiso de dos fases en REST es http://rest.blueoxen.net/cgi-bin/wiki.pl?TwoPhaseCommit, que no estropea el espacio de nombres de URL y permite un compromiso de dos fases para superponerse sobre una semántica REST limpia. – Phasmal

+3

El otro problema con esta sugerencia es que, si hay un caché de hipo y POST dos veces, obtienes dos transferencias. –

+0

Es cierto, en ese caso necesitaría tener un proceso de dos pasos: cree un recurso de "transferencia" con una URL única y luego agregue los detalles de la transferencia como parte de la confirmación (dos partes como se menciona en las otras respuestas). Por supuesto, esto podría redactarse como crear un recurso de "transacción" y luego agregarle una operación de "transferencia". – Phasmal

50

Hay algunos casos importantes que no son respondidas por esta pregunta, que creo que es una lástima, ya que tiene un alto rango en Google para los términos de búsqueda :-)

En concreto, un buen propertly Sería: si usted POSTÓ dos veces (debido a que algo del caché tuvo un hiato en el intermedio), no debe transferir la cantidad dos veces.

Para llegar a esto, crea una transacción como un objeto. Esto podría contener todos los datos que ya conoce y colocar la transacción en un estado pendiente.

POST /transfer/txn 
{"source":"john's account", "destination":"bob's account", "amount":10} 

{"id":"/transfer/txn/12345", "state":"pending", "source":...} 

vez que tenga esta transacción, puede cometerlo, algo así como:

PUT /transfer/txn/12345 
{"id":"/transfer/txn/12345", "state":"committed", ...} 

{"id":"/transfer/txn/12345", "state":"committed", ...} 

Tenga en cuenta que varios pone no importan en este punto; incluso un GET en el txn devolvería el estado actual. Específicamente, el segundo PUT detectaría que el primero ya estaba en el estado apropiado, y simplemente lo devolverá, o, si intenta ponerlo en el estado "retrotraído" después de que ya está en estado "comprometido", obtendrá un error, y la transacción confirmada real de nuevo.

Mientras hable con una única base de datos, o una base de datos con un monitor de transacciones integrado, este mecanismo realmente funcionará bien. También puede introducir tiempos de espera para las transacciones, que incluso podría expresar utilizando los encabezados Caduca si lo desea.

28

En términos REST, los recursos son sustantivos con los que se puede actuar con los verbos CRUD (crear/leer/actualizar/eliminar). Como no hay un verbo "transferir dinero", necesitamos definir un recurso de "transacción" que pueda aplicarse con CRUD. Aquí hay un ejemplo en HTTP + POX. El primer paso es CREATE (método POST HTTP) una nueva transacción vacío:

POST /transaction 

Esto devuelve un ID de transacción, por ejemplo "1234" y según URL "/ transacción/1234". Tenga en cuenta que disparar este POST varias veces no creará la misma transacción con múltiples ID y también evita la introducción de un estado "pendiente". Además, POST no siempre puede ser idempotente (un requisito de REST), por lo que generalmente es una buena práctica minimizar los datos en POST.

Puede dejar la generación de una identificación de transacción al cliente. En este caso, usted POST/transaction/1234 para crear la transacción "1234" y el servidor devolverá un error si ya existió. En la respuesta de error, el servidor podría devolver un ID actualmente no utilizado con una URL adecuada. No es una buena idea preguntar al servidor por una nueva ID con un método GET, ya que GET nunca debe alterar el estado del servidor, y la creación/reserva de una nueva ID alteraría el estado del servidor.

El siguiente, que ACTUALIZACIÓN (método HTTP PUT) la operación con todos los datos, de manera implícita cometerlo:

PUT /transaction/1234 
<transaction> 
    <from>/account/john</from> 
    <to>/account/bob</to> 
    <amount>100</amount> 
</transaction> 

Si una transacción con ID "1234" se ha puesto antes, el servidor da una respuesta de error; de lo contrario, una respuesta correcta y una URL para ver la transacción completa.

NB: en/cuenta/john, "john" realmente debería ser el número de cuenta exclusivo de John.

+3

Equating REST con CRUD es un grave error. POST no tiene que significar CREAR. –

+11

¿Un error grave? Sé que hay diferencias entre PUT y POST, pero hay un mapeo libre para CRUD. "Seriamente"? –

+2

Sí, en serio. CRUD es una forma de estructurar el almacenamiento de datos; REST es una forma de estructurar el flujo de datos de la aplicación. Puede hacer CRUD en REST, pero no puede hacer REST en CRUD. Ellos no son equivalentes. –

17

Gran pregunta, REST se explica principalmente con ejemplos similares a la base de datos, donde algo se almacena, actualiza, recupera, borra. Hay pocos ejemplos como este, donde se supone que el servidor debe procesar los datos de alguna manera. No creo que Roy Fielding haya incluido ninguno en su tesis, que estaba basada en http después de todo.

Pero sí habla de "transferencia de estado representativo" como una máquina de estado, con enlaces que pasan al siguiente estado. De esta forma, los documentos (las representaciones) realizan un seguimiento del estado del cliente, en lugar de que el servidor tenga que hacerlo. De esta forma, no hay estado del cliente, solo estado en términos del enlace en el que se encuentra.

He estado pensando en esto, y me parece razonable que para que el servidor procese algo para usted, cuando lo cargue, el servidor creará automáticamente recursos relacionados, y le dará los enlaces a ellos (en de hecho, no necesitaría crearlos automáticamente: podría simplemente decirle los enlaces, y solo los creará si los sigue y si los sigue, creación diferida). Y también para darle enlaces para crear nuevos recursos relacionados - un recurso relacionado tiene el mismo URI pero es más largo (agrega un sufijo). Por ejemplo:

  1. subes (POSTAL) la representación del concepto de una transacción con toda la información. Esto se parece a una llamada RPC, pero realmente está creando el "recurso de transacción propuesto". Por ejemplo, URI: /transaction Glitches hará que se creen múltiples recursos de este tipo, cada uno con un URI diferente.
  2. La respuesta del servidor indica el URI del recurso creado, su representación; esto incluye el enlace (URI) para crear el recurso relacionado de , un nuevo "recurso de transacción confirmada". Otros recursos relacionados son el enlace para eliminar la transacción propuesta. Estos son estados en la máquina de estado, que el cliente puede seguir. Lógicamente, estos son parte del recurso que se ha creado en el servidor, más allá de la información proporcionada por el cliente. por ejemplo URI: /transaction/1234/proposed, /transaction/1234/committed
  3. Usted POSTAL al enlace a crear el "recurso transacción comprometida", lo que crea ese recurso, cambiando el estado del servidor (los saldos de las cuentas de dos) **. Por su naturaleza, este recurso solo se puede crear una vez y no se puede actualizar. Por lo tanto, fallas que comprometen muchas transacciones no pueden ocurrir.
  4. Puede OBTENER esos dos recursos, para ver cuál es su estado. Suponiendo que un POST puede cambiar otros recursos, la propuesta ahora se marcará como "comprometida" (o tal vez, no estará disponible en absoluto).

Esto es similar a cómo funcionan las páginas web, con la página web final diciendo "¿estás seguro de que quieres hacer esto?" Esa página web final es en sí misma una representación del estado de la transacción, que incluye un enlace para pasar al siguiente estado.No solo transacciones financieras; también (por ejemplo) vista previa y luego confirmar en wikipedia. Supongo que la distinción en REST es que cada etapa en la secuencia de estados tiene un nombre explícito (su URI).

En las transacciones/ventas de la vida real, a menudo hay diferentes documentos físicos para las diferentes etapas de una transacción (propuesta, orden de compra, recibo, etc.). Aún más para comprar una casa, con liquidación, etc.

OTOH Esto se siente como jugar con la semántica para mí; No me siento cómodo con la nominalización de convertir verbos en sustantivos para hacerlo RESTful, "porque usa sustantivos (URI) en lugar de verbos (llamadas RPC)". es decir, el nombre "recurso de transacción comprometida" en lugar del verbo "comprometer esta transacción". Supongo que una ventaja de la nominalización es que puede referirse al recurso por su nombre, en lugar de tener que especificarlo de otra manera (por ejemplo, mantener el estado de la sesión para saber qué es esta transacción ...)

Pero la pregunta importante es: ¿Cuáles son los beneficios de este enfoque? es decir, ¿de qué manera es este estilo REST mejor que el estilo RPC? ¿Es una técnica ideal para páginas web también útil para procesar información, más allá de almacenar/recuperar/actualizar/eliminar? Creo que el beneficio clave de REST es la escalabilidad; un aspecto de eso no es la necesidad de mantener explícitamente el estado del cliente (pero hacerlo implícito en el URI del recurso, y el próximo indica como enlaces en su representación). En ese sentido, ayuda. Tal vez esto también ayuda en la creación de capas/canalización. OTOH solo el usuario observará su transacción específica, por lo que no hay ninguna ventaja en el almacenamiento en caché para que otros puedan leerlo, lo que representa una gran ganancia para http.

+0

¿Podría explicar cómo "no necesitar mantener el estado en el cliente" ayuda a la escalabilidad? ¿Qué tipo de escalabilidad? Escalabilidad en qué sentido? – jhegedus

1

No debe usar transacciones del lado del servidor en REST.

Uno de los contraints REST:

Stateless

La comunicación cliente-servidor está limitado aún más por ningún contexto de cliente se almacena en el servidor de entre las peticiones. Cada solicitud de cualquier cliente contiene toda la información necesaria para atender la solicitud, y cualquier estado de sesión se lleva a cabo en el cliente.

La única manera RESTful es crear un registro de rehacer la transacción y ponerlo en el estado del cliente. Con las solicitudes el cliente envía el registro de rehacer y el servidor rehace la transacción y

  1. rodillos de la transacción de vuelta, pero proporciona un nuevo registro de rehacer transacción (un paso más allá)
  2. o finalmente completar la transacción.

Pero quizás sea más sencillo utilizar una tecnología basada en sesión de servidor que admita transacciones del lado del servidor.

+0

La cita es de la entrada REST de wikipedia. ¿Es esta la verdadera fuente o la obtuvo wikipedia de alguna parte? ¿Quién puede decir cuál es el contexto del cliente y cuál es el contexto del servidor? – bbsimonbb

10

Si hace un recuento para resumir la discusión aquí, es bastante claro que REST no es apropiado para muchas API, particularmente cuando la interacción cliente-servidor es inherentemente con estado, como lo es con transacciones no triviales. ¿Por qué saltar a través de todos los argumentos sugeridos, tanto para el cliente como para el servidor, para seguir pedantemente algún principio que no se ajusta al problema? Un mejor principio es brindarle al cliente la forma más fácil, natural y productiva de componer con la aplicación.

En resumen, si realmente está haciendo muchas transacciones (tipos, no instancias) en su aplicación, realmente no debería crear una API RESTful.

+8

Correcto, pero ¿qué debería ser una alternativa en el caso de la arquitectura distribuida de micro-servicios? – Vitamon

1

Creo que sería el caso de usar un identificador único generado en el cliente para garantizar que la conexión no ocurra en una duplicidad guardada por la API.

Creo que usar un campo GUID generado por el cliente junto con el objeto de transferencia y garantizar que el mismo GUID no se reinserte nuevamente sería una solución más simple para la transferencia bancaria.

No conozca los escenarios más complejos, como la reserva múltiple de boletos de avión o las microarquitecturas.

Encontré un artículo sobre el tema, relatando las experiencias de dealing with the transaction atomicity in RESTful services.

2

En primer lugar, la transferencia de dinero no es algo que no se pueda hacer en una llamada a un solo recurso. La acción que desea hacer es enviar dinero. Entonces agrega un recurso de transferencia de dinero a la cuenta del remitente.

POST: accounts/alice, new Transfer {target:"BOB", abmount:100, currency:"CHF"}. 

Hecho. No necesita saber que esta es una transacción que debe ser atómica, etc. Simplemente transfiere dinero alias. enviar dinero de A a B.


Sin embargo, para los casos raros aquí una solución general:

Si usted quiere hacer algo muy complejo que involucra muchos recursos en un contexto definido con una gran cantidad de restricciones que en realidad cruce la barrera de qué frente a por qué (conocimiento de negocio frente a implementación) que necesita para transferir el estado. Como REST debe ser sin estado, usted como cliente necesita transferir el estado.

Si transfiere el estado, necesita ocultar la información del cliente. El cliente no debe conocer la información interna que solo necesita la implementación, pero no contiene información relevante en términos de negocios. Si esa información no tiene valor comercial, el estado debe estar encriptado y se debe usar una metáfora como token, pase o algo así.

De esta forma se puede pasar el estado interno y utilizar el cifrado y la firma del sistema puede seguir siendo segura y sólida. Encontrar la abstracción correcta para el cliente por qué él pasa información de estado es algo que depende del diseño y la arquitectura.


La verdadera solución:

Recuerde RESTO está hablando HTTP y HTTP viene con el concepto de la utilización de cookies. Esas cookies a menudo se olvidan cuando las personas hablan sobre API REST y flujos de trabajo e interacciones que abarcan múltiples recursos o solicitudes.

Recuerda lo que está escrito en la Wikipedia acerca de las cookies HTTP:

cookies fueron diseñadas para ser un mecanismo confiable para sitios web para recordar la información de estado (por ejemplo, artículos en un carro de compras) o para registrar la navegación del usuario actividad (incluyendo hacer clic en botones particulares, iniciar sesión o registrar qué páginas visitó el usuario hace unos meses o años).

Así que, básicamente, si necesita pasar el estado, utilice una cookie. Está diseñado exactamente por la misma razón, es HTTP y, por lo tanto, es compatible con REST por diseño :).


La mejor solución:

Si se habla de un cliente realiza un flujo de trabajo que involucra múltiples peticiones que suelo hablar de protocolo. Cada forma de protocolo viene con un conjunto de condiciones previas para cada paso potencial, como realizar el paso A antes de poder hacer B.

Esto es natural, pero exponer el protocolo a los clientes hace que todo sea más complejo. Para evitarlo, solo piense en lo que hacemos cuando tenemos que hacer interacciones complejas y cosas en el mundo real ... Usamos un agente.

Usando la metáfora del agente, puede proporcionar un recurso que puede realizar todos los pasos necesarios y almacenar la asignación/instrucciones reales sobre las que actúa en su lista (para que podamos usar POST en el agente o una 'agencia') .

Un ejemplo complejo:

La compra de una casa:

Usted necesita demostrar su credibilidad (como el abastecimiento de sus ingresos en el expediente de la policía), es necesario asegurarse de detalles financieros, lo que necesita para comprar la casa real utilizando un abogado y un tercero de confianza que almacena los fondos, verifica que la casa ahora te pertenece y agrega los elementos de compra a tus registros de impuestos, etc. (solo como un ejemplo, algunos pasos pueden estar equivocados o lo que sea).

Estos pasos pueden tardar varios días en completarse, algunos se puede hacer en paralelo, etc.

Con el fin de hacer esto, que acaba de dar el agente de compra de la casa como tarea:

POST: agency.com/ { task: "buy house", target:"link:toHouse", credibilities:"IamMe"}. 

Hecho. La agencia le devuelve una referencia que puede usar para ver y rastrear el estado de este trabajo y el resto lo hacen automáticamente los agentes de la agencia.

Piensa en un rastreador de errores, por ejemplo. Básicamente informa el error y puede usar el id del error para verificar qué está pasando. Incluso puede usar un servicio para escuchar los cambios de este recurso. Misión cumplida.

5

Me he alejado de este tema durante 10 años. Volviendo, no puedo creer que la religión enmascarada como ciencia en la que te metes cuando buscas en google sea más confiable. La confusión es mítica.

Yo dividiría esta amplia cuestión en tres:

  • servicios derivados. Cualquier servicio web que desarrolle tendrá servicios posteriores que utilice y cuya sintaxis de transacción no tenga más remedio que seguir. Debe intentar ocultar todo esto a los usuarios de su servicio y asegurarse de que todas las partes de su operación tengan éxito o fallar como grupo, y luego devolver este resultado a sus usuarios.
  • Sus servicios. Los clientes quieren resultados inequívocos para las llamadas al servicio web, y el patrón habitual de REST de realizar solicitudes POST, PUT o DELETE directamente sobre recursos sustantivos me parece una forma pobre y fácil de mejorar, de proporcionar esta certeza. Si le preocupa la confiabilidad, debe identificar las solicitudes de acción. Este id. Puede ser un guid creado en el cliente, o un valor inicial de un DB relacional en el servidor, no importa. Para las ID generadas por el servidor, una solicitud-respuesta está dedicada a intercambiar la identificación. Si esta solicitud falla o tiene la mitad de éxito, no hay problema, el cliente solo repite la solicitud. Los identificadores no utilizados no hacen daño.

    Esto es importante porque permite que todas las solicitudes subsiguientes sean completamente idempotentes, en el sentido de que si se repiten n veces, devuelven el mismo resultado y no causan que ocurra nada más. El servidor almacena todas las respuestas contra la identificación de la acción, y si ve la misma solicitud, repite la misma respuesta. Un tratamiento más completo del patrón está en this google doc. El documento sugiere una implementación que, creo (!), Sigue en líneas generales a los directores de REST. Los expertos seguramente me dirán cómo viola a los demás. Este patrón se puede utilizar de manera útil para cualquier llamada insegura a su servicio web, ya sea que haya o no transacciones posteriores involucradas.
  • Integración de su servicio en "transacciones" controladas por servicios ascendentes. En el contexto de los servicios web, se considera que las transacciones ACID completas normalmente no justifican el esfuerzo, pero puede ayudar enormemente a los consumidores de su servicio al cancelar y/o confirmar enlaces en su respuesta de confirmación, y así lograr transactions by compensation.

Su requisito es fundamental. No permita que la gente le diga que su solución no es kosher. Juzguen sus arquitecturas a la luz de qué tan bien y de qué manera abordan su problema.