Pregunta

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

Lea o actualice 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.

Hasta donde yo sé, la única forma de implementar esto es haciendo trampa.Puede PUBLICAR en el recurso asociado con John o Bob y realizar toda la operación mediante una sola transacción.En lo que a mí respecta, esto rompe la arquitectura REST porque esencialmente estás canalizando una llamada RPC a través de POST en lugar de operar realmente con recursos individuales.

¿Fue útil?

Solución

Considere un escenario de cesta de la compra RESTful. La cesta de la compra es conceptualmente su envoltorio de transacciones. De la misma manera que puede agregar varios artículos a una cesta de la compra y luego enviar esa cesta para procesar el pedido, puede agregar la entrada de la cuenta de Bob al contenedor de transacciones y luego la entrada de la cuenta de Bill al contenedor. Cuando todas las piezas están en su lugar, puede POSTAR / PONER el contenedor de transacciones con todas las piezas componentes.

Otros consejos

Hay algunos casos importantes que no se responden con esta pregunta, lo que creo que es una lástima, porque tiene una clasificación alta en Google para los términos de búsqueda :-)

Específicamente, una buena propiedad sería: si PUBLICA dos veces (debido a que algo de caché tiene hipo 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":...}

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 las posiciones múltiples no importan en este momento; 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ía, o, si intenta ponerlo en & Quot; rollback & Quot; estado después de que ya está en " commit " estado, obtendría un error y la transacción confirmada real volvería.

Siempre que hable con una sola 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 encabezados de Expires si lo desea.

En términos REST, los recursos son sustantivos sobre los que se puede actuar con verbos CRUD (crear / leer / actualizar / eliminar). Como no hay & Quot; transfiera dinero & Quot; verbo, necesitamos definir una " transacción " recurso sobre el que se puede actuar 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, p. & "; 1234 &"; y de acuerdo con la URL " / transacción / 1234 " ;. Tenga en cuenta que disparar esta POST varias veces no creará la misma transacción con múltiples ID y también evita la introducción de un & Quot; pendiente & Quot; estado. Además, POST no siempre puede ser idempotente (un requisito REST), por lo que generalmente es una buena práctica minimizar los datos en POST.

Puede dejar la generación de un ID de transacción al cliente. En este caso, POST / transacción / 1234 para crear la transacción & Quot; 1234 & Quot; y el servidor devolvería un error si ya existía. En la respuesta de error, el servidor podría devolver una ID actualmente no utilizada con una URL apropiada. No es una buena idea consultar al servidor para obtener una nueva ID con un método GET, ya que GET nunca debería alterar el estado del servidor, y crear / reservar una nueva ID alteraría el estado del servidor.

A continuación, ACTUALIZAMOS (PONER método HTTP) la transacción con todos los datos, comprometiéndolos 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 " ha sido PUT antes, el servidor da una respuesta de error, de lo contrario, una respuesta OK y una URL para ver la transacción completada.

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 y elimina. Hay pocos ejemplos como este, donde se supone que el servidor procesa los datos de alguna manera. No creo que Roy Fielding haya incluido ninguno en su tesis, que se basó en http después de todo.

Pero él habla de " transferencia de estado representacional " como una máquina de estado, con enlaces que se mueven al siguiente estado. De esta manera, los documentos (las representaciones) realizan un seguimiento del estado del cliente, en lugar de que el servidor tenga que hacerlo. De esta manera, no hay estado del cliente, solo el estado en términos de en qué enlace está.

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

  1. Carga ( POST ) 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 quot; recurso de transacción propuesto " ;. por ejemplo, URI: /transaction Los fallos ocasionarán la creación de múltiples recursos, 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 comprometido quot ;. 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 que proporcionó el cliente. por ejemplo, URI: /transaction/1234/proposed, /transaction/1234/committed
  3. Usted PUBLICA en el enlace para crear el " recurso de transacción comprometido " , 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, no pueden producirse fallas que comprometan muchas transacciones.
  4. 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ía como & Quot; commit & Quot; (o tal vez, no está disponible en absoluto).

Esto es similar a cómo funcionan las páginas web, con la página final que 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 (p. ej.) 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 por comprar una casa, con liquidación, etc.

OTOH Esto se siente como jugar con semántica para mí; Me incomoda la nominalización de convertir verbos en sustantivos para que sea RESTful, & Quot; porque usa sustantivos (URI) en lugar de verbos (llamadas RPC) & Quot ;. es decir, el sustantivo " recurso de transacción comprometido " en lugar del verbo " compromete 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 (como mantener el estado de la sesión, para que sepa qué & Quot; esta & Quot; transacción is ...)

Pero la pregunta importante es: ¿Cuáles son los beneficios de este enfoque? es decir, ¿de qué manera es mejor este estilo REST que el estilo RPC? ¿Es una técnica excelente 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 esto no es la necesidad de mantener el estado del cliente explícitamente (sino hacerlo implícito en el URI del recurso, y los siguientes estados como enlaces en su representación). En ese sentido, ayuda. ¿Quizás esto también ayuda en la estratificación / canalización? OTOH, solo un usuario mirará su transacción específica, por lo que no hay ninguna ventaja en almacenarla en caché para que otros puedan leerla, la gran victoria para http.

Si retrocede para resumir la discusión aquí, está bastante claro que REST no es apropiado para muchas API, particularmente cuando la interacción cliente-servidor es intrínsecamente dinámica, como lo es con las transacciones no triviales. ¿Por qué pasar por todos los aros sugeridos, tanto para el cliente como para el servidor, a fin de seguir pedantemente algún principio que no se ajuste al problema? Un mejor principio es proporcionar al cliente la forma más fácil, más natural y productiva de redactar 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.

Tendrías que tirar tu propio " ID de transacción " tipo de gestión de tx. 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 una base de datos (si la carga está equilibrada) o en la memoria o tal, luego manejar la confirmación, la reversión, el tiempo de espera.

No es realmente un día de descanso en el parque.

Me he alejado de este tema durante 10 años. Volviendo, no puedo creer la religión que se hace pasar por ciencia en la que entras cuando buscas en Google + confiable. La confusión es mítica.

Yo dividiría esta amplia pregunta en tres:

  • Servicios aguas abajo. Cualquier servicio web que desarrolle tendrá servicios posteriores que utilice, y cuya sintaxis de transacción no tiene 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 fracasen como grupo, y luego devolver este resultado a sus usuarios.
  • Sus servicios. Los clientes desean resultados inequívocos en las llamadas al servicio web, y el patrón REST habitual de realizar solicitudes POST, PUT o DELETE directamente sobre recursos sustantivos me parece una forma pobre y fácil de proporcionar esta certeza. Si le preocupa la confiabilidad, debe identificar las solicitudes de acción. Esta identificación puede ser una guía creada en el cliente, o un valor inicial de una base de datos relacional en el servidor, no importa. Para las ID generadas por el servidor, una solicitud-respuesta se dedica a intercambiar la identificación. Si esta solicitud falla o la mitad tiene éxito, no hay problema, el cliente simplemente repite la solicitud. Los identificadores no utilizados no causan ningún daño.

    Esto es importante porque permite que todas las solicitudes posteriores sean totalmente idempotentes, en el sentido de que si se repiten n veces devuelven el mismo resultado y no suceden nada más. El servidor almacena todas las respuestas contra la identificación de la acción y, si ve la misma solicitud, reproduce la misma respuesta. Un tratamiento más completo del patrón se encuentra en este documento de Google . El documento sugiere una implementación que, creo (!), Sigue ampliamente los principios de REST. Los expertos seguramente me dirán cómo viola a otros. Este patrón puede ser ú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 " controlado por servicios aguas arriba. En el contexto de los servicios web, se considera que las transacciones ACID completas generalmente no valen la pena, pero puede ayudar mucho a los consumidores de su servicio al proporcionar 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 dejes que la gente te diga que tu solución no es kosher. Juzgue sus arquitecturas a la luz de qué tan bien y cómo simplemente abordan su problema.

En primer lugar, transferir dinero no es nada que no pueda hacer en una sola llamada de recursos. La acción que quieres 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"}.

Listo. No necesita saber que esta es una transacción que debe ser atómica, etc. Simplemente transfiere dinero, también conocido como. enviar dinero de A a B.


Pero para los casos raros aquí una solución general:

Si desea hacer algo muy complejo que involucre muchos recursos en un contexto definido con muchas restricciones que realmente crucen la barrera de qué versus por qué (conocimiento de negocios versus implementación) necesita transferir el estado. Como REST no debería tener estado, usted, como cliente, debe transferir el estado.

Si transfiere el estado, debe ocultar la información del cliente. El cliente no debe conocer la información interna que solo necesita la implementación, pero no lleva información relevante en términos de negocio. Si esa información no tiene valor comercial, el estado debe cifrarse y debe usarse una metáfora como token, pase o algo.

De esta manera, se puede pasar el estado interno y usar cifrado y firmar el sistema puede ser seguro y sólido. Encontrar la abstracción correcta para el cliente por qué transmite información de estado es algo que depende del diseño y la arquitectura.


La solución real:

Recuerde que REST está hablando 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 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 fueron visitadas por el usuario desde 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 del 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 está actuando en su lista (para que podamos usar POST en el agente o una 'agencia').

Un ejemplo complejo:

Comprar una casa:

Debe demostrar su credibilidad (como proporcionar sus registros policiales), debe asegurarse de los detalles financieros, debe comprar la casa real con un abogado y un tercero de confianza que almacene los fondos, verificar que la casa ahora pertenece a usted y agregue las cosas de compra a sus registros de impuestos, etc. (solo como ejemplo, algunos pasos pueden ser incorrectos o lo que sea).

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

Para hacer esto, simplemente le da al agente la tarea de comprar casa como:

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

Listo. 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.

Piense en un rastreador de errores, por ejemplo. Básicamente, informa el error y puede usar la identificación del error para verificar qué está sucediendo. Incluso puedes usar un servicio para escucharo cambios de este recurso. Misión cumplida.

Creo que en este caso es totalmente aceptable romper la teoría pura de 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 simplemente podría aprovechar la base de datos para hacerlo.

No debe usar transacciones del lado del servidor en REST.

Una de las restricciones REST:

  

Sin estado

     

El cliente & # 8211; la comunicación del servidor se ve limitada aún más por el contexto de cliente que no se almacena 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 mantiene en el cliente.

La única forma 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. revierte la transacción pero proporciona un nuevo registro de rehacer transacción (un paso más allá)
  2. o finalmente completar la transacción.

Pero tal vez sea más simple usar una tecnología basada en sesión de servidor que admita transacciones del lado del servidor.

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

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

No sé sobre escenarios más complejos, como la reserva de múltiples boletos aéreos o micro arquitecturas.

Encontré un artículo sobre el tema, relatando las experiencias de lidiando con la atomicidad de la transacción en los servicios RESTful .

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

Entonces, para transferir entre <url-base>/account/a y <url-base>/account/b, podrías 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.

Entonces, en el momento de la publicación exitosa, la transacción "real" se lleva a cabo en el servidor y la cantidad se elimina de una cuenta y se agrega a otra.

Sin embargo, esto no cubre una transacción distribuida (si, por ejemplo, 'a' se mantiene en un banco detrás de un servicio y 'b' se mantiene en otro banco detrás de otro servicio), aparte de decir "intente expresar todo operaciones de manera que no requieran transacciones distribuidas".

Supongo que podría incluir el TAN en la URL / recurso:

  1. PUT / transacción para obtener la 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. ELIMINAR / transacción con ID 1

Solo una idea.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top