web-dev-qa-db-fra.com

Transactions en REST?

Je me demande comment vous implémenteriez le cas d'utilisation suivant dans REST. Est-il même possible de faire sans compromettre le modèle conceptuel?

Lisez ou mettez à jour plusieurs ressources dans le cadre d'une transaction unique. Par exemple, transférez 100 USD du compte bancaire de Bob sur le compte de John.

Autant que je sache, le seul moyen de mettre cela en œuvre est de tricher. Vous pouvez POST sur la ressource associée à John ou à Bob et effectuer toute l'opération à l'aide d'une seule transaction. En ce qui me concerne, cela rompt l’architecture REST parce que vous tunnelisez essentiellement un appel RPC via POST au lieu de fonctionner réellement sur des ressources individuelles.

139
Gili

Envisagez un scénario de panier de magasin RESTful. Le panier est conceptuellement votre enveloppe de transaction. De la même manière que vous pouvez ajouter plusieurs articles à un panier puis envoyer ce panier pour traiter la commande, vous pouvez ajouter l'entrée de compte de Bob au wrapper de transaction, puis l'entrée de compte de Bill au wrapper. Lorsque toutes les pièces sont en place, vous pouvez alors POST/PUT le wrapper de transaction avec toutes les pièces du composant.

85
Darrel Miller

Il y a quelques cas importants auxquels cette question ne répond pas, ce qui, à mon avis, est dommage, car Google figure en bonne position pour les termes de recherche :-)

Plus précisément, une propriété de Nice serait: Si vous POST deux fois (car certains antémémoires sont bloqués dans l’intermédiaire)), vous ne devez pas virer le montant deux fois.

Pour y arriver, vous créez une transaction en tant qu'objet. Cela pourrait contenir toutes les données que vous connaissez déjà et mettre la transaction en attente.

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

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

Une fois que vous avez cette transaction, vous pouvez la commettre, quelque chose comme:

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

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

Notez que plusieurs options de vente importent peu à ce stade. même un GET sur le txn renverrait l'état actuel. Plus précisément, le second PUT détecte que le premier est déjà dans l'état approprié et le renvoie - ou, si vous essayez de le placer dans l'état "annulé" après qu'il soit déjà dans l'état "commis", vous obtiendrez un erreur, et la transaction validée réelle en arrière.

Tant que vous parlez à une base de données unique ou à une base de données avec un moniteur de transactions intégré, ce mécanisme fonctionnera parfaitement. Vous pouvez également introduire des délais d'attente pour les transactions, que vous pouvez même utiliser à l'aide des en-têtes Expires si vous le souhaitez.

53
Jon Watte

En termes REST, les ressources sont des noms pouvant être traités avec les verbes CRUD (créer/lire/mettre à jour/supprimer). Comme il n'y a pas de verbe "transférer de l'argent", nous devons définir un " transaction "ressource pouvant être traitée avec CRUD. Voici un exemple dans HTTP + POX. La première étape consiste à CRÉER (HTTP POST méthode) une nouvelle vide transaction:

POST /transaction

Ceci retourne un ID de transaction, par exemple. "1234" et selon l'URL "/ transaction/1234". Notez que déclencher ce POST plusieurs fois ne créera pas la même transaction avec plusieurs ID et évite également l'introduction d'un état "en attente". En outre, POST peut ' t toujours être idempotent (a REST), il est donc généralement recommandé de minimiser les données dans les POST.

Vous pouvez laisser la génération d'un identifiant de transaction au client. Dans ce cas, vous auriez POST/transaction/1234 pour créer la transaction "1234" et le serveur renverrait une erreur s'il existait déjà. Dans la réponse à l'erreur, le serveur pourrait renvoyer une transaction actuellement inutilisée. ID avec une URL appropriée Il n'est pas judicieux de demander au serveur un nouvel ID avec une méthode GET, car GET ne devrait jamais modifier l'état du serveur, et la création/la réservation d'un nouvel ID modifierait l'état du serveur.

Ensuite, nous METTONS À JOUR (méthode PUT HTTP) la transaction avec toutes les données, en la validant implicitement:

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

Si une transaction avec l'ID "1234" a déjà été PUT, le serveur donne une réponse d'erreur, sinon une réponse OK et une URL pour afficher la transaction terminée.

NB: dans/account/john, "john" devrait vraiment être le numéro de compte unique de John.

30
Tuckster

Excellente question, REST est principalement expliqué par des exemples de base de données, où quelque chose est stocké, mis à jour, récupéré, supprimé. Il existe quelques exemples comme celui-ci, où le serveur est supposé traiter les données Je ne pense pas que Roy Fielding en ait inclus dans sa thèse, qui était basée sur http après tout.

Mais il parle du "transfert d'état représentationnel" en tant que machine à états, les liens passant à l'état suivant. De cette manière, les documents (les représentations) gardent une trace de l'état du client, au lieu que le serveur soit obligé de le faire. De cette manière, il n'y a pas d'état client, mais seulement en fonction du lien sur lequel vous vous trouvez.

J'y ai pensé et il me semble raisonnable que pour que le serveur traite quelque chose pour vous, lors de son téléchargement, le serveur créerait automatiquement des ressources connexes et vous indiquerait les liens les concernant (en fait, cela ne Il n’est pas nécessaire de les créer automatiquement: il peut simplement vous indiquer les liens, et il ne les crée que quand et si vous les suivez (création paresseuse). Et pour vous donner également des liens pour créer de nouvelles ressources - une ressource liée a le même URI mais est plus longue (ajoute un suffixe). Par exemple:

  1. Vous téléchargez ( POST ) la représentation du concept de transaction avec toutes les informations. Cela ressemble à un appel RPC, mais cela crée vraiment la "ressource de transaction proposée". Par exemple, URI: /transaction _ Des problèmes entraînent la création de plusieurs de ces ressources, chacune avec un URI différent.
  2. La réponse du serveur indique l'URI de la ressource créée, sa représentation - ceci inclut le lien ( URI ) pour créer la ressource associée de n nouveau "commis ressource de transaction ". Les autres ressources associées constituent le lien permettant de supprimer la transaction proposée. Ce sont des états dans la machine à états, que le client peut suivre. Logiquement, ils font partie de la ressource créée sur le serveur, au-delà des informations fournies par le client. par exemple, les URI: /transaction/1234/proposed, /transaction/1234/committed
  3. Vous PUBLIEZ sur le lien vers créez la "ressource de transaction validée", qui crée cette ressource en modifiant l'état du serveur ( les soldes des deux comptes) **. De par sa nature, cette ressource ne peut être créée qu'une seule fois et ne peut pas être mise à jour. Par conséquent, les problèmes commettant de nombreuses transactions ne peuvent pas se produire.
  4. Vous pouvez obtenir ces deux ressources, pour voir quel est leur état. En supposant qu'un POST puisse modifier d'autres ressources, la proposition serait maintenant signalée comme "engagée" (ou peut-être, non disponible du tout).

Cela ressemble à la façon dont fonctionnent les pages Web, la dernière page Web disant "êtes-vous sûr de vouloir faire cela?" Cette page Web finale est elle-même une représentation de l’état de la transaction, qui comprend un lien pour accéder à l’état suivant. Pas seulement des transactions financières; également (par exemple) prévisualiser puis commettre sur wikipedia. J'imagine que la distinction entre REST) est que chaque étape de la séquence d'états a un nom explicite (son URI).

Dans les transactions/ventes réelles, il existe souvent des documents physiques différents pour les différentes étapes d'une transaction (proposition, bon de commande, reçu, etc.). Encore plus pour l'achat d'une maison, avec règlement etc.

OTOH C'est comme jouer avec la sémantique. Je suis mal à l'aise avec la nominalisation de la conversion des verbes en noms pour le rendre RESTful, "parce qu'il utilise des noms (URI) au lieu de verbes (appels RPC)". c'est-à-dire le nom "ressource de transaction engagée" au lieu du verbe "commettre cette transaction". J'imagine que l'un des avantages de la nominalisation est que vous pouvez faire référence à la ressource par son nom, au lieu de devoir la spécifier d'une autre manière (telle que le maintien de l'état de session, de sorte que vous sachiez en quoi consiste "cette" transaction ...)

Mais la question importante est: quels sont les avantages de cette approche? En quoi ce style REST est-il meilleur que le style RPC? Une technique intéressante pour les pages Web est-elle également utile pour le traitement des informations, au-delà du stockage/récupération/mise à jour/suppression? Je pense que le principal avantage de REST est l’évolutivité; il n’est pas nécessaire de conserver explicitement l’état du client (mais de le rendre implicite dans l’URI de la ressource et les états suivants sous forme de liens). Dans ce sens, cela aide. Peut-être cela aide-t-il également à la superposition/au traitement en pipeline? OTOH seul l'utilisateur regardera leur transaction spécifique, il n'y a donc aucun avantage à la mettre en cache afin que d'autres puissent la lire, le grand gain de http .

19
13ren

Si vous restez en arrière pour résumer la discussion ici, il est assez clair que REST n'est pas approprié pour de nombreuses API, en particulier lorsque l'interaction client-serveur est intrinsèquement dynamique, comme c'est le cas avec des transactions non triviales. Pourquoi sauter dans toutes les étapes suggérées, pour le client et le serveur, afin de suivre de manière pédagogique un principe qui ne correspond pas au problème? Un meilleur principe consiste à donner au client le moyen le plus simple, le plus naturel et le plus productif de composer avec le application.

En résumé, si vous effectuez vraiment beaucoup de transactions (types, pas d'instances) dans votre application, vous ne devriez vraiment pas créer d'API RESTful.

11
Peris

Vous devrez lancer votre propre type de gestion de la transmission "id de transaction". Donc, ce serait 4 appels:

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)

Vous devez gérer le stockage des actions dans une base de données (si la charge est équilibrée) ou en mémoire, puis gérer les validations, les restaurations, les délais.

Pas vraiment une journée de repos dans le parc.

9
TheSoftwareJedi

Je me suis éloigné de ce sujet pendant 10 ans. En revenant, je ne peux pas croire que la religion se fait passer pour une science dans laquelle vous vous aventurez lorsque vous restez sur Google + fiable. La confusion est mythique.

Je diviserais cette grande question en trois:

  • Services en aval. Tous les services Web que vous développez auront des services en aval que vous utiliserez et pour lesquels vous ne devrez pas ne pas suivre la syntaxe de transaction. Vous devriez essayer de cacher tout cela aux utilisateurs de votre service et vous assurer que toutes les parties de votre opération réussissent ou échouent en tant que groupe, puis renvoyez ce résultat à vos utilisateurs.
  • Vos services Les clients veulent des résultats sans ambiguïté pour les appels de service Web. Le REST processus habituel consistant à envoyer des demandes POST, PUT ou DELETE directement à des ressources substantielles me semble être un moyen médiocre et facilement amélioré d’offrir cette certitude. Si la fiabilité vous tient à cœur, vous devez identifier les demandes d'action. Cet ID peut être un GUID créé sur le client ou une valeur de départ à partir d'une base de données relationnelle sur le serveur, peu importe. Pour les identifiants générés par le serveur, utilisez une demande-réponse 'preflight' pour échanger l'identifiant de l'action. Si cette demande échoue ou réussit à moitié, pas de problème, le client ne fait que répéter la demande. Les identifiants inutilisés ne font aucun mal.

    Ceci est important car il permet à toutes les requêtes suivantes d'être totalement idempotentes, en ce sens que si elles sont répétées n fois, elles retournent le même résultat et ne causent plus rien. Le serveur stocke toutes les réponses par rapport à l'identifiant d'action et, s'il voit la même demande, il repasse la même réponse. Un traitement plus complet du motif est en this google doc . La doc suggère une implémentation qui, je crois (!), Suit globalement REST. Les experts me diront sûrement comment cela viole les autres. Ce modèle peut être utilisé utilement pour tout appel non sécurisé à votre service Web, qu’il y ait ou non des transactions en aval.
  • Intégration de votre service dans des "transactions" contrôlées par des services en amont. Dans le contexte des services Web, les transactions ACID complètes ne sont généralement pas considérées comme rentables, mais vous pouvez grandement aider les consommateurs de votre service en fournissant des liens d'annulation et/ou de confirmation dans votre réponse de confirmation, et ainsi atteindre transactions par compensation .

Votre exigence est fondamentale. Ne laissez pas les gens vous dire que votre solution n'est pas casher. Jugez leurs architectures à la lumière de leur capacité et de leur simplicité à résoudre votre problème.

9
bbsimonbb

Tout d'abord, transférer de l'argent n'est rien que vous ne puissiez pas faire en un seul appel de ressource. L'action que vous voulez faire est d'envoyer de l'argent. Vous ajoutez donc une ressource de transfert d’argent au compte de l’expéditeur.

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

Terminé. Vous n'avez pas besoin de savoir qu'il s'agit d'une transaction qui doit être atomique, etc. Vous transférez simplement de l'argent, alias. envoyer de l'argent de A à B.


Mais dans les rares cas, voici une solution générale:

Si vous voulez faire quelque chose de très complexe impliquant de nombreuses ressources dans un contexte défini avec de nombreuses restrictions qui franchissent en réalité la barrière quoi/pourquoi (connaissances de l'entreprise par rapport à la mise en œuvre), vous devez transférer un état. Depuis REST devrait être sans état, en tant que client, vous devez transférer l'état.

Si vous transférez l'état, vous devez masquer les informations internes du client. Le client ne doit pas connaître les informations internes requises uniquement par la mise en œuvre, mais ne dispose pas d'informations pertinentes pour l'entreprise. Si ces informations n'ont aucune valeur commerciale, l'état doit être chiffré et une métaphore telle que jeton, passe ou quelque chose qui doit être utilisé.

De cette façon, il est possible de transmettre l'état interne et d'utiliser le chiffrement et la signature du système pour rester sécurisé et sain. Trouver la bonne abstraction pour le client, c'est pourquoi sa conception et son architecture dépendent des informations d'état qu'il transmet.


La vraie solution:

Rappelez-vous REST parle HTTP. HTTP utilise le concept de cookies. Ces cookies sont souvent oubliés lorsque les gens parlent de REST, ainsi que des flux de travail et des interactions multiples ressources ou demandes.

Rappelez-vous ce qui est écrit dans Wikipedia sur les cookies HTTP:

Les cookies ont été conçus pour offrir aux sites Web un moyen fiable de mémoriser des informations importantes (telles que des éléments d'un panier d'achat) ou d'enregistrer l'activité de navigation de l'utilisateur (notamment en cliquant sur des boutons particuliers, en se connectant ou en enregistrant les pages visitées par l'utilisateur). comme il y a des mois ou des années).

Donc, fondamentalement, si vous devez transmettre l'état, utilisez un cookie. Il est conçu pour exactement la même raison, il s’agit de HTTP et est donc compatible avec REST de par sa conception :).


La meilleure solution:

Si vous parlez d'un client effectuant un flux de travail impliquant plusieurs demandes, vous parlez généralement de protocole. Chaque forme de protocole est assortie d'un ensemble de conditions préalables pour chaque étape potentielle, telle que l'exécution de l'étape A avant de pouvoir effectuer l'étape B.

C'est naturel, mais exposer le protocole aux clients rend tout plus complexe. Pour éviter cela, il suffit de penser à ce que nous faisons lorsque nous devons faire des interactions complexes et des choses dans le monde réel…. Nous utilisons un agent.

En utilisant la métaphore de l'agent, vous pouvez fournir une ressource capable d'effectuer toutes les étapes nécessaires à votre place et de stocker dans sa liste les assignations/instructions sur lesquelles elle agit (afin que nous puissions utiliser POST sur l'agent ou une agence').

Un exemple complexe:

Achat d'une maison:

Vous devez prouver votre crédibilité (par exemple, fournir vos entrées dans le casier judiciaire), vous devez vous assurer des détails financiers, vous devez acheter la maison réelle en utilisant un avocat et un tiers de confiance stockant les fonds, vérifier que la maison vous appartient maintenant et ajoutez le matériel d'achat à votre dossier fiscal, etc. (à titre d'exemple, certaines étapes peuvent être fausses ou autre).

Ces étapes peuvent prendre plusieurs jours, certaines peuvent être effectuées en parallèle, etc.

Pour ce faire, il vous suffit de donner à l'agent la tâche suivante:

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

Terminé. L'agence vous renvoie une référence que vous pouvez utiliser pour voir et suivre le statut de ce travail. Le reste est fait automatiquement par les agents de l'agence.

Pensez à un traqueur de bogues par exemple. En gros, vous signalez le bogue et vous pouvez utiliser son identifiant pour vérifier ce qui se passe. Vous pouvez même utiliser un service pour écouter les modifications de cette ressource. Mission terminée.

3
Martin Kersten

Je pense que dans ce cas, il est tout à fait acceptable de casser la théorie pure de REST dans cette situation. En tout cas, je ne pense pas qu'il y ait quoi que ce soit dans REST qui indique que vous ne pouvez pas toucher aux objets dépendants dans les analyses de rentabilisation qui le nécessitent.

Je pense vraiment que ce n’est pas la peine de créer un gestionnaire de transactions personnalisé qui vous permettrait de tirer parti de la base de données.

2
Toby Hede

Vous ne devez pas utiliser de transactions côté serveur dans REST.

Une des contraintes REST:

Apatride

La communication client – ​​serveur est en outre limitée par le fait qu'aucun contexte client n'est stocké sur le serveur entre les demandes. Chaque demande d'un client contient toutes les informations nécessaires pour répondre à la demande, et tout état de session est conservé dans le client.

La seule méthode RESTful consiste à créer un journal de reprise de transaction et à le placer dans l'état du client. Avec les demandes, le client envoie le journal de reprise et le serveur rétablit la transaction et

  1. annule la transaction mais fournit un nouveau journal de transaction (un peu plus loin)
  2. ou enfin compléter la transaction.

Mais il est peut-être plus simple d'utiliser une technologie basée sur une session de serveur qui prend en charge les transactions côté serveur.

1
bebbo

Je crois que ce serait le cas d'utiliser un identifiant unique généré sur le client pour s'assurer que le hoquet de connexion n'implique pas une duplicité sauvegardée par l'API.

Je pense qu’en utilisant un champ généré par le client GUID) avec l’objet de transfert et en veillant à ce que le même GUID ne soit pas réinséré à nouveau, ce serait une solution plus simple au virement bancaire. matière.

Je ne connais pas de scénarios plus complexes, tels que la réservation de billets d'avion multiples ou des micro-architectures.

J'ai trouvé un article sur le sujet, relatant les expériences de traitant de l'atomicité des transactions dans les services RESTful .

1
Eduardo Rolim

Dans le cas simple (sans ressources distribuées), vous pouvez considérer la transaction comme une ressource où l'acte de la créer atteint l'objectif final.

Donc, pour transférer entre <url-base>/account/a et <url-base>/account/b, vous pouvez poster ce qui suit à <url-base>/transfer.

 <transfer> 
 <de> <url-base>/account/a </ from> 
 <vers> <url-base>/account/b </ to> 
 <montant> 50 </ montant> 
 </ transfer> 

Cela créerait une nouvelle ressource de transfert et renverrait la nouvelle URL du transfert - par exemple, <url-base>/transfer/256.

Au moment de la publication réussie, la transaction "réelle" est effectuée sur le serveur et le montant est supprimé d'un compte et ajouté à un autre.

Ceci, cependant, ne couvre pas une transaction distribuée (si, par exemple, "a" est détenu dans une banque derrière un service, et "b" est détenu dans une autre banque derrière un autre service) - à part dire "essaie de tout exprimer" opérations de manière à ne pas nécessiter de transactions distribuées ".

0
Phasmal