restful - Transacciones en REST?
rest significado (13)
Considere un escenario RESTful basket basket. 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.
Me pregunto cómo implementarías el siguiente caso de uso en REST. ¿Es posible hacerlo sin comprometer el modelo conceptual?
Lee o actualiza múltiples 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.
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 por los que pasaría para crear un administrador de transacciones personalizado, cuando podría aprovechar la base de datos para hacerlo.
Creo que ese sería el caso de utilizar 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 sabe acerca de escenarios más complejos, como la reserva múltiple de boletos de avión o micro arquitecturas.
Encontré un artículo sobre el tema, relatando las experiencias de tratar con la atomicidad de la transacción en servicios RESTful .
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ía la nueva url de la transferencia, por ejemplo, <url-base>/transfer/256
.
En el momento de una publicación exitosa, entonces, la transacción ''real'' se lleva a cabo en el servidor, y la cantidad se elimina de una cuenta y se agrega a otra.
Esto, sin embargo, no cubre una transacción distribuida (si, por ejemplo, ''a'' se lleva a cabo en un banco detrás de un servicio, y ''b'' se encuentra en otro banco detrás de otro servicio) - aparte de decir "intente frasear todo operaciones de formas que no requieren transacciones distribuidas ".
En primer lugar, transferir dinero no es algo que no se pueda hacer en una sola llamada a un 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.
Pero para los casos excepcionales aquí una solución general:
Si desea hacer algo muy complejo que involucre muchos recursos en un contexto definido con muchas restricciones que cruzan la barrera de qué frente a por qué (conocimiento de negocio frente a implementación) necesita 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, uno 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 que REST habla de HTTP y HTTP viene con el concepto de usar 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.
Recuerde lo que está escrito en la Wikipedia sobre las cookies HTTP:
Las cookies fueron diseñadas para ser un mecanismo confiable para que los sitios web recuerden información con estado (como artículos en un carrito de compras) o para registrar la actividad de navegación del usuario (incluyendo hacer clic en botones particulares, iniciar sesión o registrar qué páginas visitó el usuario atrás como hace meses o años).
Entonces, básicamente, si necesita pasar el estado, use 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 habla de un cliente que realiza un flujo de trabajo que involucra múltiples solicitudes, generalmente habla sobre el 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 para usted 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:
Comprando una casa:
Debes demostrar tu credibilidad (como proporcionar tus registros policiales), debes garantizar los detalles financieros, debes comprar la casa real con un abogado y un tercero de confianza que almacena los fondos, verificar que la casa ahora te pertenece y agregue el material de compra a sus registros de impuestos, etc. (solo como un ejemplo, algunos pasos pueden estar equivocados o lo que sea).
Es posible que estos pasos tarden varios días en completarse, algunos se pueden realizar en paralelo, etc.
Para hacer esto, le da al agente la tarea comprar la casa como:
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.
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 CREAR (método HTTP POST) una nueva transacción vacía :
POST /transaction
Esto devuelve una ID de transacción, por ejemplo, "1234" y según la 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.
A continuación, ACTUALIZAMOS (PUT HTTP method) la transacción con todos los datos, comprometiéndolo implícitamente:
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 PUT antes, el servidor da una respuesta de error, de lo contrario, una respuesta OK y una URL para ver la transacción completa.
NB: en / account / john, "john" realmente debería ser el número de cuenta único de John.
Gran pregunta, REST se explica principalmente con ejemplos similares a bases 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 sobre la "transferencia de estado representacional" 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 (de hecho, no lo haría). No es necesario que los cree automáticamente: podría decirle los enlaces, y solo los creará si los sigue y si los sigue: creación lenta). 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:
- Usted carga ( POST ) la representación del concepto de 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 de/transaction
hará que se creen múltiples recursos de ese tipo, cada uno con un URI diferente. - 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 comprometido". 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
- Usted PUBLICA al enlace para crear el "recurso de transacción comprometida" , que crea ese recurso, cambiando el estado del servidor (los saldos de las dos cuentas) **. 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.
- Puedes 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, y la página web final dice "¿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 diferentes etapas de una transacción (propuesta, orden de compra, recibo, etc.). Aún más para comprar una casa, con un asentamiento, etc.
OTOH Esto me parece jugar con la semántica; 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 alguna otra forma (como mantener el estado de la sesión, para que sepa 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.
Hay algunos casos importantes que no han sido respondidos por esta pregunta, que creo que es una lástima, ya que tiene una clasificación alta en Google para los términos de búsqueda :-)
Específicamente, una buena propiedad sería: si POSTAS dos veces (debido a que algo del caché tiene un hiato en el intermedio) no debes 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":...}
Una vez que tenga esta transacción, puede confirmarla, algo así como:
PUT /transfer/txn/12345
{"id":"/transfer/txn/12345", "state":"committed", ...}
{"id":"/transfer/txn/12345", "state":"committed", ...}
Tenga en cuenta que múltiples puts 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 sola base de datos o con 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.
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 pregunta en tres:
- Servicios de Downstream. 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 posteriores 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 este documento de Google . 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 proporcionando enlaces de cancelación y / o confirmación en su respuesta de confirmación, y así lograr transacciones por compensación .
Su requerimiento 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.
No debe usar transacciones del lado del servidor en REST.
Una de las desventajas de REST:
Apátrida
La comunicación cliente-servidor se ve limitada por el hecho de que no se almacena ningún contexto de cliente en el servidor entre las solicitudes. 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 forma RESTful es crear un registro de rehacer transacciones 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
- devuelve la transacción pero proporciona un nuevo registro de rehacer transacciones (un paso más allá)
- o finalmente complete la transacción.
Pero tal vez sea más simple usar una tecnología basada en sesiones de servidor que admita transacciones del lado del servidor.
Si se detiene 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 ocurre con las 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.
Supongo que podría incluir el TAN en la URL / recurso:
- PUT / transacción para obtener la ID (por ejemplo, "1")
- [PUT, GET, POST, lo que sea] / 1 / account / bob
- [PUT, GET, POST, whatever] / 1 / cuenta / factura
- BORRAR / transacción con ID 1
Solo una idea.
Tendría que lanzar su propio tipo de gestión de tx "id de transacción". Entonces serían 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)
Tendría que manejar el almacenamiento de las acciones en un DB (si está equilibrado de carga) o en la memoria o algo así, y luego manejar el commit, rollback, timeout.
No es realmente un día RESTANTE en el parque.