読者です 読者をやめる 読者になる 読者になる

Block Rockin’ Codes

back with another one of those block rockin' codes

Trigger と Stream ベースの Reactive スタイルについて考える

intro

先日 Meteor について調べて発表するにあたり色々調べたり、そのあと何人かの方々とお話させて頂いた中で、
思うところが出てきたので、アウトプットしたいと思います。


Meteor について発表してきました。 - Block Rockin’ Codes


Meteor 以前からも思っていたところもありますが、ちょうどいいので Meteor も対比に出しながら書いてみます。

Reactive Programming

@ さんも書いてたように、やはり一番大きいのはここだと思います。


Meteor.js - naoyaのはてなダイアリー


簡単に説明すると、

よく Excel に例えられますが、あるセルに他のセルの値を用いた計算式を埋めておくと、
その参照先のデータが変わった時に、式を埋め込んだセルの値も連動して変わるようなあれです。


プログラミングで見るなら、
例えば以下の場合、 b を出した後、
a の値を変えても、 b の値は変わりませんが、、

var a = 1;
var b = a + 1;

a = 2
console.log(b); // 2

リアクティブプログラミング的には
a が変わると、連動して b も変わる感じ。

var a = 1;
var b = a + 1;

a = 2
console.log(b); // 3

になるという感じです。


Reactive スタイルな Web

"Reactive なプログラミング" 自体は、先ほどの説明の通り「言語自体のサポート」無しでは書けません。
(先のようなコードは、 JS では動かないという意味)


しかし、このアイデアは「データソースに変更があると、 View に反映される」という置き換えを行うことで、
Web アプリ全体のアーキテクチャとして持ち込むことができます。


Meteor の場合は、バックにある DB 内のデータと、フロントの View に埋め込まれた変数を bind しておくと、
DB の変更が WebSocket で通知されたタイミングで、bind 先の View が更新されるようになっています。


こうした動作を Sock.js という Socket.IO みたいなモジュールを使って WebSocket(など) を用いた形で行なっています。
言語自体が Reactive な動作(?) をサポートしているわけではありませんが、全体のアーキテクチャが Reactive なスタイルというイメージです。


Meteor には多くの機能がありますが、自分が一番良いと思ったのは、この Reactive スタイルなアプリ開発を、
うまいこと抽象化して、それなりに簡単に実現できるようにしているところです。

なぜ Reactive か?


「リアルタイム Web」とくくられるアプリケーションに共通しているのは、
おそらく「サーバの変化を、素早く View に持ってきている」パターンだと思います。
チャットなども、 PtoP で無い限り、サーバを介しているので同じように考えられます。


そして、サーバの変化をフロントに持ってくるには、

  1. サーバの変化のイベントを取得
  2. そのイベントでリモートにイベント(やデータ)を Push
  3. クライアントはイベントを受け取ったら View を更新
  4. これら全体のハンドリング

というような形になります。


これを、先ほどの Reactive なスタイルを適応すると、とても素直に実現できます。


ちなみに Meteor では、それぞれ

  1. Pub/Sub をベースとしたイベント通知
  2. Socke.js を用いた Push
  3. liveui を用いた bind による更新
  4. ReactiveContext


を用いて実現しているようです。


ちょっとこれと照らし合わせて詳しく見てみます。

1.DB トリガーの再発見(かどうかはわからないけど)


発表でも言ったんですが、
最近は、 "DB トリガー" があれば、このアーキテクチャの 1 の部分がとてもすっきり書き直せるんじゃないかと思います。


トリガーは、例えばインサートが走った時に、そのクエリの終了時にイベントを発生し、
登録しておいた処理を実行するような機能です。


もし DB がトリガーをサポートしていなかったら、DB へのインサートをフックしたい場合、
プログラム側でイベントを生成する必要が出るでしょう。


変更を取得するために、 DB をポーリングする必要があるかもしれません。
Meteor では、インサートと同時に Pub/Sub でそれを通知しているようです。


こうした処理を、自前でやるのは、イベントの生成、例外処理、場合によっては MQ なども絡んできて割と面倒です。
ポーリングはパフォーマンスの問題も出ます。


しかし、トリガーがあると、インサートが(成功/失敗問わず)完了したことを、DB からのイベントとして取得することができます。
プログラムはトリガーからのイベントを待つだけで、面倒なところの大半をトリガーがやってくれるので、非常に見通しがよくなると思います。


しかし、リアルタイム性の高いアプリに DB の Trigger を持ち込むと、 Trigger 自体のパフォーマンスがかなり重要になってきます。
これらを踏まえた上で、今現実的に「使える」 Trigger を持つ DB がどのくらいあるのかは、自分は把握できていないんですが(多分 Oracle はいけるけど、お高い。。)
今後 DB 自体にこうした機能があるととても嬉しい場面は多いと思います。


感覚的にあまり取り上げられることが少ないように思うこの Trigger という機能が、今こそもっと見直されるべきだと思います。
そして、このままだと、リアルタイム Web のバックとしては、必須の機能になるんじゃないかと、個人的には思います。


そのうち、各 DB のトリガーのパフォーマンスも調べてみたいところです。
(ちなみにブラウザの localStrage なんかも change イベントを発生するんですが、これは別のタブに通知するためらしい。。なぜ。。)


2.通信手段

「リアルタイム Web」などと言われている文脈の中で、WebSocket なんかが一緒に盛り上がってる感じもありますが、
結局 WebSocket でリアルタイム感が出ているのは「サーバが Push をすることができる」という点が重要で、


つまり本当は WS でなくてもできます。ただ、 WS でやったほうが、効率がいい場合が多いということになります。
というか、 WS はそのためのプロトコルだし、これ以外にも ServerSentEvent や SPDY なんかもこれを実現するために使えるでしょう。
すべてを生 WebSocket でやる必要は無いと思います。


純粋な通信以外(認証、ネームスペース etc) はいらないなら、 Socket.IO じゃなくてもいい。


ここは完全に、通信量やパフォーマンスなどで使い分ける必要があると思います。

3.bind

View にバインドするパターンは MVVM 由来でいくつかあるらしいです。
knockout.js や Derby.js あたりがそうみたいだけど、あまり詳しくないのでわからない。

Reactive と Stream


やさしいFunctional reactive programming(概要編) - maoeのブログ から引用すると

Wikidediaから抜粋するとFRPにおいて重要なポイントは次の通りです。

入力はbehavior(振る舞いとかビヘイビア)か時間で変化するイベントストリームとして見える
時間にともない連続的(continuous)に変化する値を扱える(これがbehavior)
時間順に並ぶ離散的(discrete)なevent
時間にともない変化する値は、higer orderかもしれない(要するに普通の値じゃないかもしれない)


その下に behavior と event のわかりやすい説明があります。
ちょっと多いですが、まるっと引用させて頂きます。

behaviorの一番簡単な例は時間です。ある値tが時間を表すbehaviorだとすると、値tは時とともに連続的に変化します。
tを出力するとある時点の時刻が(サンプリングされて)表示されますが、内部的には連続的に変化しています。
他にもユーザが動かしているマウスの座標だったり、日照によって変化するボンネットの温度や、振り子運動しているブランコの速度、
刻々と変化する株価など、behaviorは実にありふれています。

一方で、eventとは時間と値が組になったストリームです。たとえばユーザからのキー入力を表すkeyという値を考えます。
keyはユーザがキーを押した時刻tと、どのキーかを表すcを組にした(t, c)という値のストリームを表しています。
他にもユーザがマウスをクリックするごとに発生するeventや、ボンネットの温度が5度あがるごとに発生するevent、
ブランコの速度が0になるたびに発生するevent、あるいは株式市場で取引の開始・終了を知らせるeventなど、eventもまたありふれています。

さて、これってどこかで聞いたことないでしょうか?

実は、 Node における Stream の概念が、まさしくこれらを抽象化していると見ることができます。


Node の Stream については、かつて以下に書きましたのでご参照下さい。


Node.js の Stream API で「データの流れ」を扱う方法 - Block Rockin’ Codes


簡単に説明すると、 Stream は「時間」と「値」を「データの流れ」として抽象化するためのインタフェース。というような位置づけになります。
連続して変化するデータを、イベントと合わせて扱うことができます。


つまり、 Node の Stream は、Reactive スタイルのアプリケーション開発と非常に相性がよさそうです。


例えば、 DB の変更を Stream として読み込み、データが変更されるたび(data イベントが起こるたびに) WebSocket でそれをフロントに送り、
フロントは WebSocket でデータを受け取るたびに、それを View に反映するといった感じ。


この場合は DB を ReadableStream として抽象化して、 Client (の View) を WritableStream として抽象化したものを、
pipe() で繋いだら完成です。


つまり

ServerReadableStream.pipe(ClientWritableStream);


です。逆も同じ。
つまりアーキテクチャ全体を Stream で抽象化できます。


で、これを 「Stream ベースの Web」として考えたのが、先の Stream の記事の次に書いた、下記の記事です。


Stream.IO というものを作ってます。 - Block Rockin’ Codes


このアイデアから生まれたのが Jxck/stream.io · GitHub です。
ということで、今まで考えてきたことが繋がったなぁと(汗


以上の話で、

  1. DB がイベントを生成するための Trigger
  2. イベント(とデータ) をリモートとやりとりするための WS などの通信手段
  3. View にデータを bind し更新するためのなんらかのエンジン
  4. それらを抽象化、ハンドリング(pipe) するための Stream


があれば、 Meteor とは違った、コンパクトなリアクティブプログラミングフレームワークになりそうです。
でも、3. は knockout.js とか delby.js あたりを使えば良さそうだし、 Stream.IO の責務ではないかな。



実は Stream.IO は、まだブラウザで node の Stream を動かすために EventEmitter, Stream, Assert などを移植して
力尽き 方向性を見失っていたんですが、色々着想が得られたので、もう少し頑張ってみようかなと思います。

outro

そんなことを GW 中考えていました。


リアクティブと合わせてトリガーにちょっと注目して行きたいです。


いつもどおり、ご指摘があればお願いします。