web-dev-qa-db-ja.com

イベントソーシングとREST

イベントソーシングの設計に出会い、RESTクライアントが必要(正確にはRESTful)であるアプリケーションで使用したいのですが、RESTは完全にCRUDに似ており、イベントソーシングはタスクベースです。RESTサーバーへのリクエストに基づいてコマンドの作成をどのように設計できるかを考えていました。この例を考えてみます:

RESTを使用すると、Fileと呼ばれるリソースに新しい状態を設定できます。1つのリクエストで、新しいファイル名を送信でき、親フォルダを変更したり、ファイルの所有者を変更したりできます。 。

イベントソーシングを使用できるようにサーバーを構築する方法。私はこれらの可能性について考えていました:

  1. 変更されたフィールドをサーバーで特定し、適切なコマンド(RenameFileCommandMoveFileCommandChangeOwnerCommand、...)を作成し、これらを個別にディスパッチします。ただし、この設定では、各コマンドが失敗して他のトランザクションが失われ、リソースへの「アトミック」な変更が失われます。

  2. 1つのコマンド(UpdateFileCommand)だけをディスパッチし、コマンドハンドラー、より正確には集約でディスパッチして、変更されたフィールドを判別し、代わりに個々のイベントを送信します(FileRenamedEventFileMovedEventOwnerChangedEvent、...)

  3. これは私はまったく好きではありません。UIはまだタスクベースであるため(サーバーとの通信はRESTを介して行われるため)、サーバーへのリクエストで使用するコマンドをヘッダーに指定します。ただし、他のREST通信(外部アプリなど)の使用)では、1つのリクエストで1つのフィールドのみを変更する必要がないため、失敗します。また、 UI、REST、ESベースのバックエンド。

あなたはどちらを好みますか、これを処理するためのより良い方法はありますか?

補足:Javaおよびイベントソーシング用のAxon Frameworkで記述されたアプリ).

17
redhead

ここで実装の不一致に対するユーザープロセスがあるかもしれません。

まず、ユーザーはファイルに複数の変更を同時に正直に実行しますか?名前の変更(パスの変更が含まれる場合と含まれない場合があります)、所有権の変更、およびファイルの内容の変更(議論のため)は、個別のアクションのように見えます。

答えが「はい」の場合を考えてみましょう-ユーザーは本当にこれらの変更を同時に行いたいと思っています。

その場合、これを表すために、複数のイベントを送信する実装-RenameFileCommandMoveFileCommandChangeOwnerCommand-を強くお勧めします singleユーザーの意図。

どうして?イベントが失敗する可能性があるためです。非常にまれなことかもしれませんが、ユーザーがアトミックに見える操作を送信しました-ダウンストリームイベントの1つが失敗した場合、アプリケーションの状態は無効になります。

また、各イベントハンドラ間で明確に共有されているリソースにレースハザードを招いています。コマンドを受信するまでにファイル名とファイルパスが古くなる可能性があるため、「ChangeOwnerCommand」を書き込む必要があります。

ファイルを移動して名前を変更する非イベント駆動のレストフルシステムを実装するときは、eTagシステムのようなものを使用して一貫性を確保することを好みます。編集中のリソースのバージョンがユーザーが最後に取得したバージョンであることを確認し、失敗した場合は失敗します。それ以来変更されています。ただし、この単一のユーザー操作に対して複数のコマンドをディスパッチする場合は、各コマンドの後にリソースバージョンをインクリメントする必要があります。そのため、ユーザーが編集しているリソースが実際に最後に読み取ったリソースと同じバージョンであることを知る方法はありません。 。

つまり、誰かがほぼ同時にファイルに対して別の操作を実行するとどうなるでしょうか。 6つのコマンドは任意の順序でスタックできます。アトミックコマンドが2つしかない場合、前のコマンドは成功し、後のコマンドは「最後に取得されてからリソースが変更されている」ために失敗する可能性があります。しかし、コマンドがアトミックでない場合、これに対する保護はないため、システムの一貫性が損なわれます。

興味深いことに、RESTのイベントベースのアーキテクチャに似た動きがあり、「RESTなしのPUT」と呼ばれ、 Thoughtworksテクノロジーレーダー、2015年1月 で推奨されています。 ここにPUTなしの休憩 に関するかなり長いブログがあります。

基本的に、POST、PUT、DELETE、GETは小規模なアプリケーションでは問題ないという考え方ですが、プット、ポスト、および削除が反対側でどのように解釈されるかを想定する必要がある場合は、カップリングを導入します。 (たとえば、「銀行口座に関連付けられているリソースを削除すると、口座は閉鎖されます」)そして、提案されたソリューションは、RESTをよりイベントソースで処理することです。すなわちLets POST単一のイベントリソースとしてのユーザーの意図。

他のケースはより簡単です。ユーザーがこれらすべての操作を同時に実行したくない場合は、許可しないでください。 POST各ユーザーインテントのイベント。これで、リソースでetagバージョン管理を使用できます。

リソースに対して非常に異なるAPIを使用している他のアプリケーションについては。それは問題のようなにおいがします。 RESTful APIの上に古いAPIのファサードを構築して、それらをファサードに向けることができますか?つまり、REST server?

古いソリューションの上にRESTfulインターフェースを構築することも、RESTソリューションの上に古いインターフェースのファサードを構築することもせず、両方のAPIが共有データリソースを指すように維持することを試みる場合、あなたは大きな頭痛を経験するでしょう。

11
perfectionist

ちょうど今、私は次の記事に出くわしました。これは、Content-Typeヘッダーでサーバーへのリクエストのコマンド名を指定することを奨励します(メディアタイプの5つのレベルに従っています)。

記事では、RPCスタイルはRESTに不適切であると述べており、Content-Typeを拡張してコマンド名を指定することを提案しています。

一般的なアプローチの1つは、RPCスタイルのリソース(/ api/InventoryItem/{id}/renameなど)を使用することです。これにより、任意の動詞の必要性が一見なくなりますが、RESTのリソース指向のプレゼンテーションには反します。リソースは名詞であり、HTTP動詞は動詞/アクションであり、自己記述メッセージ(RESTの理念の1つ)は、情報と意図の他の軸を伝達する手段であることを思い出してください。実際、HTTPメッセージのペイロード内のコマンドは、任意のアクションを表現するのに十分なはずです。ただし、通常は本文がストリームとして配信され、アクションを識別する前に本文全体をバッファリングするため、メッセージの本文に依存すること自体に問題があります。ここでは、コマンドタイプがContent-Typeヘッダーのパラメーターとして提示される5LMTのレベル4(ドメインモデル)に基づくアプローチを示します。

PUT /api/InventoryItem/4454c398-2fbb-4215-b986-fb7b54b62ac5 HTTP/1.1
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Content-Type:application/json;domain-model=RenameInventoryItemCommand`

記事はこちら: http://www.infoq.com/articles/rest-api-on-cqrs

メディアタイプの5つのレベルの詳細については、こちらをご覧ください。 http://byterot.blogspot.co.uk/2012/12/5-levels-of-media-type-rest-csds.html


彼らはドメインイベントをREST APIに公開していますが、これは悪い習慣だと思いますが、CQRS専用の新しい "プロトコル"を作成しないため、ソリューションが気に入っています。本文または追加ヘッダーでコマンド名を送信し、RESTful原則に忠実であり続けます。

4
redhead