前半はまずこの話から入るが、後半ではRESTに関する間違いについて、3つほど思うところを述べたい。
楽観的排他制御と2phase commit
reflexworksではFeedやEntry単位でatomicなトランザクション処理を行えるが2phase commitはサポートしていない。これを許すと密結合になってスケールしないからである。だが、これはあくまで同期的な処理の話であって、ネットワーク障害への耐性を考慮され、非同期処理やオフラインで使えるのであれば2phase commitでもスケールすると考えている。
RESTにおける2phase commitについては、REST におけるトランザクションについて (Re: Web を支える技術) が詳しい。
RESTにおける2phase commitについては、REST におけるトランザクションについて (Re: Web を支える技術) が詳しい。
トランザクションレイヤを RESTful に表現する手法は理解しておいて損がないものだと思います。具体的には、
- トランザクションの開始は POST を使ったトランザクションリソースの生成
- トランザクション開始後は POST ではなく PUT を使い、クライアントがリクエスト ID を指定することで、ネットワーク障害への耐性を確保
- トランザクションのコミットも再送可能じゃないと困るので PUT
- コミットの返り値を確認してからトランザクションリソースを DELETE
といったあたりが基本になると思います。
ポイントは、クライアントがリクエスト ID を指定する点。IDはPOSTで返却されたリソースIDになるが、楽観的排他制御のためには、論理シーケンス番号 (LSN)も同時に必要になるはずだ。
もう少し具体的に説明しよう。
もう少し具体的に説明しよう。
前述の例では自分しか更新しない前提ではあるが複数回実行される可能性は考慮すべきところで、べき等性の保証が必要なのだが、この場合は楽観的排他エラーで「既に更新されています」というメッセージを出してあげる方が親切だと思われる。
ちなみに、同一リソースURLでリビジョン番号も同じであれば、システム全体で同じものであることが保証されている。これは、複数のノードに散在するデータの整合性を保証するためのreflexworksの基本的な考えにもなっている。(エンタープライズクラウドにおけるスケールアウトの現実解)
また、POSTはリソース作成時にのみ使い、PUTは既存のリソース更新に使うのはrelfexworksも同様である。「 一般に、リソース作成時に PUT を使うべきでないとされる理由は、クライアント間で名前の競合が発生する可能性があるから」という理由はもちろんだが、CRUDの4つは明確に区別したいという理由もある。SQLにおいても、INSERTとUPDATEは区別されている。
RESTはステートレスでなければならないか
さて、ここからRESTの3つの間違いの話に入る。ステートレスはRESTの原理原則であり、実際に疎結合にすることでシステムはスケールラブルになる。私は、ステートレスをスケーラビリティ確保の絶対条件と考え、reflexworksではセッションは使わない方針を貫いてはいた。そして、ログイン処理においてさえセッションIDは使わずに都度認証することを徹底していた。たしかにステートレスにするとスケールはする。だが、最近は度が過ぎるとかえって不自由な設計を招いてしまうことになることがわかってきて、逆にセッションはアリなんじゃないかと思うようになってきた。
はっきりいおう。私は間違っていた。
一つは、ユーザログイン管理において毎回認証を実行させる必要があり、擬似的なセッションIDを発行する必要があったこと。具体的には、認証に成功したら新たに時間制限をつけたcookieチケット(ワンタイムパスワード)を発行し擬似的なセッションIDで対応していた。また、そのチケットはあくまでログイン認証用であり、セッションオブジェクトは管理しないようにしていた。ユーザにログイン入力を強要させるわけでもなく、名前もStateless Session ID(SSID)にしていた。
しかし、ユーザIDとワンタイムパスワードを含むトークンを毎回クライアントと通信してやりとりするのはセキィリティ上よろしくない。私は頑にステートレスを守る一方でセキュリティをないがしろにしていたわけだ。
また、パフォーマンスも悪くなる。
前述した2phase commitでは、払い出したチケット(/buy/934085,1)は一時的な揮発性の情報であり、これらはむしろセッションオブジェクトかキャッシュメモリに置くべきである。こうすることで、パフォーマンスも格段によくなり、KVSとの間にメモリという緩衝材が入ることでI/O負荷も軽減できる。
というわけで、最近はRESTであっても必要最低限のセッションオブジェクトだけは認めてもいいと考えるになった次第である。実際に最新版のreflexworksでは標準のセッションオブジェクトが使えるようになっている。また、一時的なオブジェクトはなるべくキャッシュさせるような設計を推奨している。
リソースは本当にドメインロジックを必要としないか
2つ目の間違いはリソースとドメインロジックの置く場所についてである。MVCの話のなかでも触れてはいるが、ドメインロジックはバウンダリだけでなくコントロールやエンティティの層においても構わない。
にもかかわらず、当初のreflexworksでは、リソースを特定するURLに対して単純なHTTPリクエストのCRUD操作(GET,PUT,POST,DELETE)しか用意していなかった。
これだと、例えばECサイトの金額チェックなどをサーバ側で行うことができない。金額を正確にチェックするにはサーバ側でマスター参照する必要があるはずである。たとえ認証を付けたところで、リクエストデータが正しいかどうかという話は別である。どんなに頑張ってクライアントでチェックしてもサーバ側では無条件では受付けられない。
だからといって、自由にサーバ側のインターフェースを作らせたくはない。
サービスを自由に作らせると、きっとこんな感じのインターフェースになってしまうだろう。
entry = getFoo(entry)
entry = updateFooByBar(entry)
・・・
これでは、サービス志向の二の舞になってしまう。せっかく、RESTでリソースを一意に定義できているのだから、こういう感じにしたい。
entry = Foo.service(entry)
ちなみに、/d/Fooに対して実行すると、/d servletが呼び出され、以前のバージョンと同様に、サービスは実行されずにリソースの内容がそのまま返される仕組みとなっている。(/dはdata、/pはproviderの頭文字)
最近では、単純にデータだけが提供される、BaaS(Backends as a Service)やDBaaS(Database as a Service)が流行っているが、本当にサービスのドメインロジックが必要ないか、よく検討して使うべきだろう。
イベントドリブンなアーキテクチャーは受け入れられるか
先日、MOVEが話題になった。コントローラをOperationsとEventsに分けた方がいいという案だが、望まれなかった子にあるように、MVCの一つの解釈(あるいは間違えているの)であり、MVCを否定するものではない。
ただ、closureや、あるいは、Deferredのような非同期処理をなんとか中心に考えたいという気持ちもわからないでもない。
実は、reflexworksでもイベントドリブンな設計はいけると思った時期があった。実際にイベントをトリガーにサーバサイドJSを実行できる。でも開発者の多くが難しいと感じ、オーソドックスな方を好んだので、今は廃止しようと考えている。オーソドックスな方とは、前述したような単純な/p servletによるフィルターのような処理のことである。
単にユニークであることが使いやすいとは限らない。
また、コントローラをOperationsとEventsに分けるより大事だと思うのは、Modelをデータ(スキーマ)とドメインロジックに分けることである。スキーマは一貫させる一方でドメインロジックは各レイヤの最適な場所に配置させる。貧血症みたいなオブジェクトにはなってしまうが、それを恐れてはいけない。
以上が、RESTに関して私が犯した間違いの3点である。
だが、もしかしたら、こうやって原理原則を修正した結果、RESTのアーキテクチャーとは到底呼べないものになってしまっているかもしれない。だがそれでも構わない。なぜなら、これらは実務に落としたときに実際に問題となって一つ一つ解決してきたものだからである。
今流行のリーンスタートアップ流にいうなら、クールなアーキテクチャだと思って採用(Build)するのは結構なことだが、測定(Measure)して、学習(Learn)してから最終的に判断しないとだめである。それが本当に素晴らしいかどうかは、実務に落としたとき初めてわかるものだから。
<ご紹介>
reflexworks <= 製品についてはこちら
reflexworksで分業開発しよう! <= 開発案件募集中です