MakingSenseofStreamProcessing / Streaming All the Way to the User Interface


Streaming All the Way to the User Interface

要約

著者いわく「まとめに入る前にOne More Thing(最高のネタを最後に残しておいたのですよ!)」

詳細

アプリケーションのユーザーが何かのデータを見るときに、何が起こるのかを考えてみよう。 昔ながらのデータベースアーキテクチャなら、データベースからデータを読み込み、ビジネスロジックに基づいてなんらかの変換をして、キャッシュに書き込んだりするかもしれない。そしてキャッシュされたデータを何らかの方法でUI上にレンダリングする。HTMLにしてサーバーで表示させたり、JSON形式でクライアントに送ってクライアント側でレンダリングさせたり。

テンプレートレンダリングの結果は、UIのレイアウトを表すものになる。 たとえばウェブブラウザならHTML DOM、ネイティブアプリならOSのUIコンポーネントを使うことになるだろう。 いずれにせよ、最終的には、レンダリングエンジンがこれを画像メモリ内のピクセルに変換して、グラフィックデバイスで表示できるようにする。

そう考えるとこれは、データ変換パイプラインに似ている(Figure 5-24)。 それぞれのレイヤーが、ひとつ上のレイヤーのマテリアライズドビューだと考えられる。 キャッシュはデータベースのマテリアライズドビュー(キャッシュは、データベースの内容から得られたもの)。 HTML DOMはキャッシュのマテリアライズドビュー(HTMLは、キャッシュ内のJSONから作られたもの)。 ビデオメモリのピクセルはHTML DOMのマテリアライズドビュー(ブラウザの描画エンジンは、UIレイアウトに基づいてピクセルをレンダリングする)。

msos_0524.png

Figure 5-24. Rendering data on screen requires a sequence of transformation steps, not unlike materialized views.

さて、これらの変換はどれくらいうまくできているものだろう? ブラウザのレンダリングエンジンは、すばらしい出来だと思っている。 JavaScript?を使ってCSSクラスを変更したり、マウスオーバー時にCSSを適用したりもできるし、それらの変更に伴って再描画が必要になる範囲も自動的に判断してくれる。 ハードウェアアクセラレーションされたアニメーションや3D変換も行う。 ビデオメモリのピクセルは自動的に最新の状態に保たれていて、これらの複雑な変換がきわめてうまく機能する。

データオブジェクトからUIコンポーネントへの変換はどうだろう? 現時点では「まあまあ」といったところだろうか。データの更新に伴ってUIを更新させるテクニックは、まだ産まれて日が浅いものだ。 ただ、急速に成熟しつつある。Webの世界ではReactAngularEmberなどのフレームワークが登場し、ストリームから更新情報を受け取れるようなUIを実現できる。 Elmのような関数型リアクティブプログラミング言語も同様だ。 この分野はさまざまな活動があって、よい方向に向かいつつある。

データベースからキャッシュへの変換は、このパイプラインの中ではもっとも弱いところだろう。 問題は、キャッシュがリクエスト指向である点だ。クライアントからキャッシュを読むことはできるが、キャッシュが更新されたかどうかを知る手段がない(定期的にポーリングすることはできるけどね……)。

私たちはいま、中途半端な状況にある。UIロジックとブラウザのレンダリングエンジンは、データの変更にあわせて動的に画面のピクセルを更新できる。 その一方で、データベースに基づくバックエンドのサービスは、クライアントにデータの変更を伝える手段を持たない。 ユーザーの入力に対して迅速に反応する必要のあるアプリケーション(リアルタイム共同作業アプリなど)を作るには、このパイプラインをエンドツーエンドでスムーズに流れるようにしなければいけない。

幸い、ストリームプロセッサを使ってマテリアライズドビューを作れば、足りなかった部分もカバーできる(Figure 5-25)。

msos_0525.png

Figure 5-25. If you update materialized views by using an event stream, you can also push changes to those views to clients.

クライアントがマテリアライズドビューから読み込んだときに、そのコネクションをオープンしたままにしておける。 その後、ストリーム内で発生したイベントによってビューが更新されたら、サーバーはこの接続を使ってクライアントに通知できる (たとえばWebSocketsServer-Sent Eventsを使えばいい)。 それを受けたクライアントは、UIを更新すればいい。

つまり、クライアントはただ単にある時点のビューを読んだだけではなく、実際にはその後に発生する変更のストリームを購読したことになる。 クライアントがネットにつながり続けている限り、サーバーはその後の変更をクライアントにプッシュできる。 そしてクライアントは、ただちにそれを描画できるわけだ。 最新情報が手に入るのなら、画面の情報を古いままにしておく手はない。 リクエストしたそのときの情報しか表示されない静的なウェブページなんて、時代遅れになりつつある。

でも、クライアントがデータの変更を購読できるようにするには、アプリケーションの書きかたを大きく見直す必要がある。 私たちの頭の中もネットワークプロトコルもプログラミング言語も、リクエスト・レスポンス形式のモデルに縛られすぎている。 RESTfulサービスへのリクエストだろうがオブジェクトのメソッド呼び出しだろうが、「何らかのリクエストに対して何らかのレスポンスがある」というのが前提だ。

msos_0526.png

Figure 5-26. To support dynamically updated views we need to move away from request/response RPC models and use push-based publishsubscribe dataflow everywhere.

この固定観念から離れる必要がある。 リクエストとレスポンスではなく、ストリームの購読と購読者へのイベント通知という考え方に切り替えよう(Figure 5-26)。 データベースもクライアントライブラリもアプリケーションサーバーもビジネスロジックもフロントエンドも、あらゆるレイヤーにおいてこの切り替えが必要になる。 データの変更にあわせて動的にUIを更新するためには、あらゆる場面にストリーム脳で立ち向かわないといけない。そうすれば、データの変更がすべてのレイヤーに伝播できるようになる。

いまどきのRESTful APIやデータベースドライバやWebアプリケーションフレームワークは、そのほとんどがリクエスト/レスポンス形式を前提としているので、ストリーミングデータフローをサポートしようとすると苦労するだろう。でも今後は、ストリーミングにやさしいプログラミングモデルを使う人が増えてくると思っている。その例を図1-31でとりあげた。

すでにそういった動きもある。 たとえばRethinkDBは、クエリの結果が変わったときにクライアントに通知できる仕組みを用意している*1MeteorFirebaseはデータベース層とUI層を統合して、変更をUIにプッシュできるフレームワークだ。 こんなのがもっともっと増えるといいな(Figure 5-27)。

msos_0527.png

Figure 5-27. Event streams are a splendid idea. We should put them everywhere.

担当者のつぶやき

みんなの突っ込み



*1 Slava Akhmechet: "Advancing the realtime web," rethinkdb.com, 27 January 2015.