RESTでのトランザクション?


147

RESTで次のユースケースをどのように実装するのでしょうか。概念モデルを損なうことなく行うことさえ可能ですか?

単一のトランザクションのスコープ内で複数のリソースを読み取るか更新します。たとえば、ボブの銀行口座からジョンの口座に$ 100を転送します。

私の知る限り、これを実装する唯一の方法は不正行為です。JohnまたはBobのいずれかに関連付けられたリソースにPOSTし、単一のトランザクションを使用して操作全体を実行できます。私が懸念している限りでは、これはRESTアーキテクチャーを壊します。なぜなら、実際には個々のリソースを操作するのではなく、POSTを介してRPC呼び出しをトンネリングしているからです。

回答:


91

RESTfulな買い物かごのシナリオを考えてみましょう。買い物かごは、概念的にはトランザクションラッパーです。買い物かごに複数のアイテムを追加し、そのバスケットを送信して注文を処理するのと同じように、ボブのアカウントエントリをトランザクションラッパーに追加し、次にビルのアカウントエントリをラッパーに追加できます。すべてのピースが配置されたら、すべてのコンポーネントピースでトランザクションラッパーをPOST / PUTできます。


18
TransferMoneyTransactionが実行可能な銀行リソースにならないのはなぜですか?
ダレルミラー

8
エンドポイントが名詞を参照するようにすると、通常、標準のGET、PUT、POST、DELETE動詞がその名詞に対して何を行うかが直感的にわかります。RPCでは、エンドポイント自体を動詞にすることができるため、エンドポイントはHTTP動詞と競合する可能性があり、意図がわかりにくくなります。
ダレルミラー

10
たとえば、エンドポイントUpdateXYZでHTTP DELETEを実行するとどうなりますか?XYZは削除されますか?更新を削除しますか、それとも単に更新を実行してHTTP動詞の削除を無視しますか?動詞をエンドポイントから遠ざけることで、混乱を取り除きます。
Darrel Miller

5
そして、複数のサービスにまたがるトランザクションはどうですか?そして、サービスが暗黙的なトランザクションコンテナーを公開しない一連の「無関係な」変更を行いたい場合はどうでしょうか。さらに、実際のデータとは完全に無関係な汎用トランザクションに移行するときに、特定のトランザクションタイプが必要なのはなぜですか。変更。トランザクションはRESTfulと一致しない場合がありますが、トランザクションは上に階層化する必要があるようですが、リクエストヘッダーにトランザクション参照が含まれているという事実を除けば、残りの呼び出しとは無関係です。
meandmycode 2010

4
@meandmycodeデータベーストランザクションは、RESTインターフェイスの背後に階層化する必要があります。または、ビジネストランザクション(データベーストランザクションではない)をそれ自体をリソースとして公開し、障害が発生した場合の対応策を講じる必要があります。
Darrel Miller、

60

この質問では答えられないいくつかの重要なケースがありますが、Googleでは検索語のランキングが高いため、これは残念です。

具体的には、適切なのは次のとおりです。POSTを2回実行する場合(中間キャッシュで一部のキャッシュが占有されるため)、2回転送しないでください。

これを行うには、トランザクションをオブジェクトとして作成します。これには、すでに知っているすべてのデータが含まれ、トランザクションを保留状態にすることができます。

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

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

このトランザクションを取得したら、次のようにコミットできます。

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

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

この時点では、複数のプットは関係ありません。txnでGETを実行しても、現在の状態が返されます。具体的には、2番目のPUTは、最初のPUTがすでに適切な状態にあることを検出し、それを単に返します。または、すでに「コミット済み」状態になっている後に「ロールバック」状態にしようとすると、エラー、および実際にコミットされたトランザクションが戻されます。

単一のデータベース、またはトランザクションモニターが統合されたデータベースと通信する限り、このメカニズムは実際には問題なく機能します。さらに、トランザクションのタイムアウトを導入することもできます。タイムアウトは、必要に応じてExpiresヘッダーを使用して表現することもできます。


興味深い議論!最初の投稿は1つのステップで実行する必要があることを付け加えておきます。後で追加することはできません(ショッピングカートの領域にあり、ショッピングカートにはエンドユーザーに害を及ぼすことを防ぐための多くの小切手と残高があり、法律でさえ銀行振込ではありません)...
Erk

33

RESTの用語では、リソースはCRUD(作成/読み取り/更新/削除)動詞で処理できる名詞です。「送金」動詞はないため、CRUDで処理できる「トランザクション」リソースを定義する必要があります。これはHTTP + POXの例です。最初のステップは、することですCREATE(HTTP POSTメソッド)新しい空のトランザクションを:

POST /transaction

これは、「1234」などのトランザクションIDと、それに対応するURL「/ transaction / 1234」を返します。このPOSTを複数回起動しても、複数のIDを持つ同じトランザクションは作成されず、「保留中」の状態の導入も回避されることに注意してください。また、POSTは常にべき等であるとは限らないため(REST要件)、POST内のデータを最小限に抑えることをお勧めします。

トランザクションIDの生成はクライアントに任せることができます。この場合、POST / transaction / 1234を実行してトランザクション「1234」を作成し、サーバーがすでに存在する場合はエラーを返します。エラーレスポンスでは、サーバーは現在使用されていないIDと適切なURLを返す可能性があります。GETはサーバーの状態を決して変更しないため、GETメソッドでサーバーに新しいIDを照会することはお勧めできません。新しいIDを作成または予約するとサーバーの状態が変更されます。

次に、トランザクションをすべてのデータで更新(PUT HTTPメソッド)し、暗黙的にコミットします。

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

ID "1234"のトランザクションが以前にPUTされた場合、サーバーはエラー応答を返します。それ以外の場合、OK応答と完了したトランザクションを表示するためのURLを返します。

注:/ account / johnでは、「john」は実際にはJohnの一意のアカウント番号である必要があります。


4
RESTをCRUDと同等にすることは重大な間違いです。POSTはCREATEを意味する必要はありません。

12
重大な間違い?PUTとPOSTには違いがあることは知っていますが、CRUDへのマッピングが緩やかです。「マジ」?
テッドジョンソン

3
はい、真剣に。CRUDは、データストレージを構造化する方法です。RESTは、アプリケーションのデータフローを構造化する方法です。RESTでCRUDを実行できますが、CRUDでRESTを実行することはできません。それらは同等ではありません。
Jon Watte、2014年

20

すばらしい質問です。RESTのほとんどは、データベースのような例で説明されています。ここでは、何かが保存、更新、取得、削除されます。この例のように、サーバーが何らかの方法でデータを処理することになっている例はほとんどありません。ロイ・フィールディングが彼の論文に何も含まなかったと私は思う。結局のところ、それはhttpに基づいていた。

しかし彼は、リンクを次の状態に移動させて、状態マシンとしての「表現状態転送」について話します。このようにして、サーバーがクライアントの状態を追跡する代わりに、ドキュメント(表現)がクライアントの状態を追跡します。このように、クライアントの状態はありません。現在のリンクに関する状態のみが示されます。

私はこれについて考えていましたが、サーバーに何かを処理させるために、アップロードすると、サーバーが関連リソースを自動的に作成し、それらへのリンクを提供することは理にかなっているようです(実際には、それらを自動的に作成する必要はありません。リンクを通知するだけで、フォローした場合にのみリンクを作成します-遅延作成)。また、新しい関連リソースを作成するためのリンクも提供します-関連リソースは同じURIを持ちますが、長くなります(サフィックスが追加されます)。例えば:

  1. すべての情報を含むトランザクションの概念の表現をアップロード(POSTします。 これはRPC呼び出しと同じように見えますが、実際には「提案されたトランザクションリソース」を作成しています。例:URI:/transaction グリッチにより、それぞれが異なるURIを持つ複数のリソースが作成されます。
  2. サーバーの応答は、作成されたリソースのURIとその表現を示します。これには、新しい「コミットされたトランザクションリソース」の関連リソースを作成するためのリンク(URI)が含まれますその他の関連リソースは、提案されたトランザクションを削除するためのリンクです。これらは、クライアントが追跡できるステートマシンの状態です。論理的には、これらはクライアントが提供した情報を超えて、サーバー上に作成されたリソースの一部です。例えばURIを:/transaction/1234/proposed/transaction/1234/committed
  3. あなたPOSTするためのリンクに「コミットされたトランザクションのリソース」を作成し、サーバ(2つの口座の残高)の状態を変え、そのリソースを作成し、**。その性質上、このリソースは一度しか作成できず、更新することはできません。したがって、多くのトランザクションをコミットするグリッチは発生しません。
  4. これら2つのリソースを取得して、それらの状態を確認できます。POSTが他のリソースを変更できると仮定すると、提案は「コミット済み」としてフラグが立てられます(または、おそらくまったく使用できません)。

これはWebページの動作に似ており、最後のWebページには「これを実行してもよろしいですか?」その最終的なWebページは、それ自体がトランザクションの状態の表現であり、次の状態に進むためのリンクが含まれています。金融取引だけではありません。また(例えば)プレビューしてからウィキペディアでコミットします。RESTの違いは、状態のシーケンスの各ステージに明示的な名前(URI)があることです。

実際のトランザクション/販売では、トランザクションのさまざまな段階(提案、発注書、受領書など)ごとに異なる物理的なドキュメントが存在することがよくあります。家の購入、決済などにさらに役立ちます。

OTOHこれは私にとってセマンティクスで遊んでいるような気がします。「動詞(RPC呼び出し)の代わりに名詞(URI)を使用するため」動詞を名詞に変換してRESTfulにする名詞化には不快です。つまり、「commit this transaction」という動詞の代わりに、「committed transaction resource」という名詞。名義化の利点の1つは、リソースを名前で参照できることです。リソースを他の方法で指定する必要はありません(たとえば、セッション状態を維持するため、「この」トランザクションが何であるかがわかります)。

しかし重要な質問は次のとおりです。このアプローチの利点は何ですか?つまり、このRESTスタイルはRPCスタイルよりもどのように優れていますか?Webページに最適な手法は、保存/取得/更新/削除以外に、情報の処理にも役立ちますか?RESTの主な利点はスケーラビリティだと思います。その1つの側面は、クライアントの状態を明示的に維持する必要がないことです(ただし、リソースのURIでそれを暗黙的にし、次の状態をその表現のリンクとして)。その意味で役立ちます。おそらくこれはレイヤー化/パイプライン化にも役立ちますか?OTOHは1人のユーザーだけが特定のトランザクションを見るので、それをキャッシュしても他のユーザーが読み取れるという利点はなく、httpの大きな利点です。


「クライアントの状態を維持する必要がない」がスケーラビリティにどのように役立つかを説明していただけませんか?どのような拡張性ですか?どのような意味でのスケーラビリティ?
jhegedus

11

ここでの議論を要約すると、RESTは多くのAPIには適切ではないことは明らかです。それは、クライアントサーバーの対話が本質的にステートフルであり、重要なトランザクションの場合と同様です。クライアントとサーバーの両方について、問題に適合しないいくつかの原則に徹底的に従うために、提案されているすべてのフープをジャンプするのはなぜですか?より良い原則は、アプリケーションを構成する最も簡単で自然で生産的な方法をクライアントに提供することです。

要約すると、実際にアプリケーションで多くのトランザクション(インスタンスではなくタイプ)を実行している場合、RESTful APIを作成するべきではありません。


9
そうですが、分散型マイクロサービスアーキテクチャの場合、代替手段は何でしょうか?
Vitamon 2014

11

私はこのトピックから10年間離れていました。戻って、私はあなたがグーグル休憩+信頼できるときにあなたが踏み込む科学になりすました宗教を信じることができません。混乱は神話です。

この幅広い質問を3つに分けます。

  • 下流のサービス。開発するすべてのWebサービスには、使用するダウンストリームサービスがあり、そのトランザクション構文に従う必要があります。これをすべてサービスのユーザーから隠し、操作のすべての部分がグループとして成功または失敗することを確認してから、この結果をユーザーに返します。
  • あなたのサービス。クライアントはWebサービスの呼び出しに対して明確な結果を望んでおり、実質的なリソースに対して直接POST、PUT、またはDELETEリクエストを行うという通常のRESTパターンは、この確実性を提供する方法として貧弱で、簡単に改善できるものとして私を襲います。信頼性を重視する場合は、アクションリクエストを特定する必要があります。このIDは、クライアントで作成されたGUIDでも、サーバー上のリレーショナルDBからのシード値でもかまいません。サーバーで生成されたIDの場合、「プリフライト」リクエスト-レスポンスを使用してアクションのIDを交換します。このリクエストが失敗するか、半分成功した場合、問題はありません。クライアントはリクエストを繰り返すだけです。未使用のIDは害を及ぼしません。

    これは、後続のすべてのリクエストを完全にべき等にすることができるため、重要です。リクエストがn回繰り返されると、同じ結果が返され、それ以上何も起こらないという意味です。サーバーは、アクションIDに対するすべての応答を保存し、同じ要求を見つけた場合、同じ応答を再生します。パターンのより完全な扱いは、このgoogle docにあります。このドキュメントは、RESTプリンシパルに幅広く準拠していると私が信じる実装を提案しています(!)専門家はきっと他の人にどのように違反しているのか教えてくれるでしょう。このパターンは、関与するダウンストリームトランザクションの有無にかかわらず、Webサービスへの安全でない呼び出しに有効に利用できます。
  • アップストリームサービスによって制御される「トランザクション」へのサービスの統合。Webサービスのコンテキストでは、完全なACIDトランザクションは通常、努力する価値がないと見なされますが、確認応答にキャンセルおよび/または確認リンクを提供することにより、サービスの消費者を大幅に支援し、補償によってトランザクションを達成できます。

あなたの要件は基本的なものです。あなたの解決策がユダヤではないとあなたに言わせてはいけません。彼らがあなたの問題にどれだけうまく、そしてどれほど簡単に対処しているかに照らして、彼らのアーキテクチャを判断してください。


9

独自の「トランザクションID」タイプのトランザクション管理をロールする必要があります。したがって、4つの呼び出しになります。

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)

アクションの格納をDB(負荷分散されている場合)またはメモリなどに処理してから、コミット、ロールバック、タイムアウトを処理する必要があります。

本当に公園でのRESTfulな日ではありません。


4
これは特に良いイラストだとは思いません。必要な手順は、トランザクションの作成(「保留」状態のトランザクションを作成する)とコミットトランザクション(コミットされていない場合はコミットし、リソースをコミットまたはロールバック状態に移動する)の2つだけです。
Jon Watte

2

この場合、この状況でRESTの純粋な理論を破ることは完全に許容できると思います。いずれにせよ、RESTには、それを必要とするビジネスケースで依存オブジェクトに触れることができないと言っているものはないと思います。

データベースを活用してカスタムトランザクションマネージャーを作成する場合に、ジャンプして追加のフープを追加する価値はないと思います。


2

まず、送金は、1回のリソース呼び出しで実行できないことではありません。あなたがしたい行動は送金です。したがって、送金者のアカウントに送金リソースを追加します。

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

できました。これはアトミックである必要があるトランザクションであることを知る必要はありません。単に送金を別名で行うだけです。AからBに送金します。


しかし、ここではまれなケースの一般的な解決策:

定義されたコンテキストで多くのリソースを必要とする非常に複雑なことをしたい場合は、実際にwhatとwhyの障壁(ビジネスと実装の知識)を越える多くの制限があり、状態を転送する必要があります。RESTはステートレスである必要があるため、クライアントとしての状態を転送する必要があります。

状態を転送する場合、内部の情報をクライアントから隠す必要があります。クライアントは、実装にのみ必要な内部情報を知っているべきではありませんが、ビジネスの観点から関連する情報を持ちません。これらの情報にビジネス上の価値がない場合は、状態を暗号化し、トークン、パスなどのメタファーを使用する必要があります。

このようにして、内部状態を渡し、暗号化と署名を使用して、システムを安全かつ確実にすることができます。クライアントが状態情報を渡す理由をクライアントに適切に抽象化することは、設計とアーキテクチャ次第です。


実際のソリューション:

RESTはHTTPを話し、HTTPにはCookieを使用するという概念が付属していることを思い出してください。それらのCookieは、人々がREST APIとワークフロー、および複数のリソースまたはリクエストにまたがる相互作用について話すとき、しばしば忘れられます。

ウィキペディアでHTTP Cookieについて書かれていることを思い出してください。

Cookieは、Webサイトがステートフルな情報(ショッピングカート内のアイテムなど)を記憶したり、ユーザーの閲覧アクティビティ(特定のボタンのクリック、ログイン、ユーザーがアクセスしたページの記録など)を記録したりするための信頼できるメカニズムとして設計されました数か月または数年前に戻します)。

したがって、基本的に状態を渡す必要がある場合は、Cookieを使用します。それはまったく同じ理由で設計されています。それはHTTPであり、したがって設計によりRESTと互換性があります:)。


より良い解決策:

複数のリクエストを含むワークフローを実行するクライアントについて話す場合、通常はプロトコルについて話します。すべてのプロトコル形式には、Bを実行する前にステップAを実行するなど、潜在的なステップごとに一連の前提条件が付属しています。

これは自然なことですが、クライアントにプロトコルを公開すると、すべてがより複雑になります。それを避けるために、現実の世界で複雑な相互作用やことをしなければならないときに私たちが何をすべきかを考えてください。エージェントを使用します。

エージェントのメタファーを使用すると、必要なすべての手順を実行できるリソースを提供し、実際の割り当て/それが実行されている指示をリストに格納できます(そのため、エージェントまたは「代理店」でPOSTを使用できます)。

複雑な例:

家を買う:

信頼性を証明する必要があります(警察の記録を提供するなど)、財務の詳細を確認する必要があります。弁護士と資金を保管している信頼できるサードパーティを使用して実際の家を購入し、家が自分のものであることを確認する必要があります。税務記録などに購入用のものを追加します(例として、いくつかの手順は間違っているか、または何でもかまいません)。

これらの手順は完了するまでに数日かかる場合があります。並行して実行できる手順もあります。

これを行うには、次のようにエージェントにタスク購入ハウスを与えるだけです。

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

できました。代理店は、このジョブのステータスを確認および追跡するために使用できる参照をあなたに送り返します。残りは代理店のエージェントによって自動的に行われます。

たとえば、バグトラッカーについて考えてみましょう。基本的にはバグを報告し、バグIDを使用して何が起こっているかを確認できます。サービスを使用して、このリソースの変更をリッスンすることもできます。ミッション完了。


1

RESTではサーバー側トランザクションを使用しないでください。

REST制約の1つ:

ステートレス

クライアントとサーバーの通信は、リクエスト間でサーバーにクライアントコンテキストが保存されないことによってさらに制約されます。クライアントからの各要求には、要求を処理するために必要なすべての情報が含まれており、セッション状態はクライアントで保持されます。

RESTfulな唯一の方法は、トランザクションREDOログを作成してクライアント状態にすることです。リクエストにより、クライアントはREDOログを送信し、サーバーはトランザクションを再実行し、

  1. トランザクションをロールバックしますが、新しいトランザクションREDOログを提供します(さらに1ステップ)
  2. または最後にトランザクションを完了します。

しかし、サーバー側のトランザクションをサポートするサーバーセッションベースのテクノロジーを使用する方が簡単な場合があります。


引用はウィキペディアのRESTエントリからのものです。これは本当の情報源ですか、それともウィキペディアがどこから入手したのですか?クライアントコンテキストとサーバーコンテキストは何と言うのでしょうか。
bbsimonbb 2016

1

これは、クライアントで生成された一意の識別子を使用して、接続の中断がAPIによって保存された重複を意味しないことを保証する場合に当てはまると思います。

クライアントで生成されたGUIDフィールドを転送オブジェクトと共に使用し、同じGUIDが再度挿入されないようにすることは、銀行振込の問題に対するより簡単な解決策になると思います。

複数の航空券予約やマイクロアーキテクチャなど、より複雑なシナリオについては知らない。

RESTfulサービスでトランザクションの原子性を処理する経験に関連する、この件に関する論文を見つけました。


0

単純な場合(分散リソースなし)では、トランザクションをリソースと見なすことができ、トランザクションを作成することで最終目的が達成されます。

したがって、との間<url-base>/account/aで転送<url-base>/account/bするには、以下にを投稿できます<url-base>/transfer

<乗り換え>
    <from> <url-base> / account / a </ from>
    <to> <url-base> / account / b </ to>
    <amount> 50 </ amount>
</転送>

これにより、たとえば新しい転送リソースが作成され、転送の新しいURLが返されます<url-base>/transfer/256

投稿が成功した時点で、サーバー上で「実際の」トランザクションが実行され、金額が1つのアカウントから削除され、別のアカウントに追加されます。

ただし、これは分散トランザクションをカバーしていません(たとえば、「a」が1つのサービスの背後にある1つの銀行で保持され、「b」が別のサービスの背後で別の銀行で保持されている場合)-「すべてをフレーズしてみてください分散トランザクションを必要としない方法での操作」。


2
「すべての操作を分散トランザクションを必要としない方法で表現する」ことができない場合は、実際には2フェーズコミットが必要です。RESTで2フェーズコミットを実装するために私が見つけることができる最良のアイデアは、rest.blueoxen.net / cgi-bin / wiki.pl?TwoPhaseCommitです。クリーンなRESTセマンティクス。
Phasmal

3
この提案のもう1つの問題は、キャッシュが2回中断し、POSTが2回発生すると、2つの転送が行われることです。
Jon Watte、2011年

True、その場合、2つのステップのプロセスが必要になります。一意のURLで「転送」リソースを作成し、転送の詳細をコミットの一部として(他の回答で述べたように2つの部分で)追加します。もちろん、これは「トランザクション」リソースを作成し、それに「転送」操作を追加することで表現できます。
Phasmal

-3

URL /リソースにTANを含めることができると思います:

  1. IDを取得するためにPUT / transaction(例: "1")
  2. [PUT、GET、POSTなど] / 1 / account / bob
  3. [PUT、GET、POSTなど] / 1 / account / bill
  4. ID 1のDELETE / transaction

ただのアイデア。


このアプローチには2つの問題があります。1)トランザクションの外部でリソースにアクセスできないことを意味します(ただし、これは大した問題ではないかもしれません)。2)サーバーがもはやステートレスではなくなったという事実にこれまでのところどの回答も触れていませんが、それについては何もできないと思います。
ギリ

/ 1 / account / bobと/ account / bobは、2つの異なるリソースです。:)そしてRE:ステートレスです。これは、リソースが常に利用可能で、前のリクエストに依存していないことを意味します。トランザクションを要求したので、そうではありません。しかし、再び、トランザクションが必要でした。
までの

1
クライアントがURIをアセンブルする必要がある場合、APIはRESTfulではありません。
aehlke 2009

1
本当に理解できません!(上記の例のように)トランザクションをリソースとして扱う場合は、古典的な意味でのトランザクションの扱いをやめて、「適切なRESTの方法」でそれを利用するだけで、トランザクションプロセスのプログラミングがさらに簡素化されます。たとえば、応答にトランザクションへのhrefを含めることで、分散サーバーサイド環境での置き換えを回避できます。それはまだステートレスであり(単なるリソースですよね)、実際のトランザクションメカニズムを実装できます。欲しい(後ろにDBがない場合はどうなるか?)
Matthias Hryniszak

1
SQL / SOAPの考え方をやめ、HTTPの考え方を始めれば(ブラウザーのように)すべてが単純になります
Matthias Hryniszak
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.