今現にポピュラーに使われているWeb技術、つまりHTTPとHTMLだけで分散アプリケーションは作れます。もっとも、「分散アプリケーション」(あるいは「分散コンピューティング」)という言葉の定義によりけりですが; ここでは、「インターネット上に散らばったデータリソースや計算リソースを協調させて何かをするアプリケーション」くらいの意味。いたって雑駁でライトウェイトな定義です。
「Webアプリケーションの入出力と状態遷移」において、Webアプリケーションの基本動作は代入文と同じだと言いました。随分とひどい単純化をしたものです。が、単純化しておけばその後の説明が楽だと思ったのです。単純化した定式化=代入文に基づいてライトウェイトな分散アプリケーションの説明をします。
内容:
- リモート実行とローカル代入
- 長い距離を表す矢印
- Catyにロングパイプを
- ロングパイプを隠蔽せよ
- Web空間上にグローバルなフローグラフを描く
リモート実行とローカル代入
1回のHTTPトランザクションによるWebアプリケーションの処理と効果は、次の代入文でした。(「Webアプリケーションの入出力と状態遷移」を参照。)
/*
* a : アプリケーション状態を保持する変数
* d : 文書を保持する変数
* r : リクエスト
* f : サーバー側処理
*/(a, d) := f(a, r);
面倒なので、(a, d) をy、(a, r) をxと1文字で書けば、y := f(x); です。xがWebアプリケーションへの入力、f(x)がWebアプリケーションの出力で、変数yがブラウザ状態です。「:=」は破壊的代入で、直前のyの値がなんであっても、f(x)で上書きされます。f, g, hなどがインターネット上に散らばる様々なWebアプリケーションだとすると、ブラウザyはそれらにリクエストを送って、その戻り値(処理結果の出力)で自分自身の値(状態)を書き換えていきす。次のような感じ:
y := f(x1);
y := f(x2);
y := g(x3);
y := h(x4);
y := f(x5);
Webアプリケーションf, g, hなどの入力であるx1, x2, ... などにはブラウザの状態(=直前のyの値)の一部がアプリケーション状態として含まれるかもしれません。リクエストURLやPOSTデータなども含まれるでしょう。代入文の右辺がfからgに変わったのはハイパーリンクをたどったせいかもしれません。hからfに戻ったのはBACKボタンを押したのでしょうか。
関数f, g, hなどの実行環境(つまりサーバー)とブラウザであるyは、通常は遠く離れています。この「離れた感じ」を強調するために、代入演算子を :=<- と書くことにします。矢印「<-」が距離感の象徴です。f(x)の戻り値(レスポンス)がインターネットを旅して変数yを上書きすることを、f :=<- f(x); と書くのです。ブラウザから見ると、f(x)の実行はリモートでなされ、最終的な代入(状態遷移)はローカルで実行されます。
長い距離を表す矢印
Webアプリケーションの処理が複数の関数の結合(合成)で実現されているときは、代入文は次のように書けます。
- y :=<- g(f(x));
Ajaxリクエストによりサーバー側でfが走り、gがJavaScriptによる処理なら:
- y := g(<- f(x));
と書けばいいでしょう。「:=」はローカル代入で、「<-」は長い距離を表します。fの出力は長い距離(「<-」)を旅しますが、f(x)を受け取って処理したgの出力は即座にyに反映されます。だって、この例のgはブラウザ側で動いてますからね。
では、次はどうでしょう。
- y :=<- f2(<-g(<-f1(x)))
これだけだと事情がハッキリしません。f1とf2はサイトAで動いていて、gは別なサイトBで動いているとしましょう。すると、次のように解釈できます。
- リクエストxは、サイトAのf1に入力され、処理結果は f1(x) です。
- その結果はブラウザに返されるのではなくて、サイトBのgに送られます。
- サイトのBで動くgの処理結果 g(<-f1(x)) はサイトAに戻されます。
- サイトAのf2は、サイトBの処理結果を入力として、f2(<-g(<-f1(x))) を計算します。
- その結果がブラウザに送られて、ブラウザ状態yを更新します。
この例では、サイトAにおけるリクエスト処理が、別なサイトBが提供しているWebサービス/WebAPIを利用していることになります。関数gの出力を保持する一時変数(サイトA上のバッファ)を y' とすると、中間処理は次のように書けます。
y' :=<- g(x'); // x' は f1(x)
この代入文の意味は、サイトAがHTTPクライアントとなって、サイトB上の処理gをリモート実行していることに他なりません。
Catyにロングパイプを
Catyでは、g(f(x)) のような書き方の代わりに左から右に x | f | g というパイプライン記法を使います。右端がパイプラインの出口で、処理結果=レスポンスは右側から外に出ていきます。ブラウザ状態である変数yへの代入まで左から右の方向で書けば:
- x | f | g ->=: y
となるでしょう。矢印「->」が、インターネット上を長い距離走ることを表しています。y :=<- f2(<-g(<-f1(x))) なら、次のように書けるでしょう。
- x | f1 |-> g ->| f2 ->=: y
記号「|->」「->|」をロングパイプと呼ぶことにします。通常のパイプと機能は同じですが、データを長い距離に渡って運ぶパイプです。「|->」がローカルからリモートへとデータを流すパイプ、「->|」がリモート出力をローカルまで引っ張ってくるパイプです。*1
ロングパイプを実現するには、リモートへのHTTPリクエストを実行するリクエスタが必要です。g' というローカルで動く関数(Catyではコマンドと呼ぶ)があり、このg'がgへのリクエストを行うとします。g'は、gの代理人(プロキシ、サロゲート)であり、遠くにいる本人gに処理を移譲します。
リクエスタg'を使うと、ロングパイプを含むパイプライン x | f1 |-> g ->| f2 は、 x | f1 | g' | f2 というローカルパイプラインで実現できます。記号的には、g' = (->g->) ですね。これは、リクエスタg'のなかに遠い往復経路 (-> ・ ->) がカプセル化されていることを表しています。
ロングパイプを隠蔽せよ
ロングパイプを表す記号「|->」「->|」を、Catyスクリプトの構文に入れるか? それはないです! 記号「|->」「->|」は説明に使うための記号です。こんな記号を入れたらめんどくさくてたまりません。
分散アプリケーション・フレームワークの役割は、ローカルとリモートを透過的に扱えるようにすることです。gがリモートコマンドであっても、それを意識せずに x | f1 | g | f2 のように書けないといけません。
もちろん、内部メカニズムとしてはロングパイプは実在します。そのメカニズムは既に述べたごとくリモートサービスへのリクエスタです。リクエスタは、対象となるリモートサービスのエンドポイント、使用されるデータ形式、プロトコル(通信手順)などを知っている必要があります。しかし、個々のリモートサービスごとにリクエスタをいちいち作成しなくても、パラメトライズされた汎用リクエスタでも対応はできそうです。(コマンドではなく)シェルが一律にロングパイプを処理できればサイコーです*2。
http-get http://remote.example.com/index.html とか http-post http://remote.example.com/do-something.cgi のように使う素朴なコマンドはすぐ作れるでしょうが、これはあんまりうれしくないのです。リモートサービスの入力/出力の型がサッパリ分からず、強い型システムと整合しません*3。リモートサービスをきちんとラップしたコマンドなら、入出力の型を明示的に宣言することができて、通常のローカルコマンドと同じように安全に使えます。Catyの強い型システム/コマンドシステムのなかで、離れた地点にあるコマンド達を繋ぐロングパイプラインを実現できるんじゃないかな、と思ってます
Web空間上にグローバルなフローグラフを描く
Catyスクリプトは、本来的に図式言語(pictorial/graphical/diagrammatic language)で、同一メモリ空間内にある複数の処理単位(コマンド)を繋ぎあわせた処理フローグラフ*4を記述するものです。同一メモリ空間という制限を外して、ロングパイプを実現すれば、インターネット上に散在した複数の処理単位を繋ぎあわせたグローバルなフローグラフを描けるでしょう。例えばCatyには、もともとローカルのマッシュアップ機構*5が付いていますが、これと、ロングパイプを組み合わせれば、インターネット上の分散リソース群を再編成するグローバルなマッシュアップができます。
マイクロフォーマット(または類似仕様)とかクリーンHTMLも分散アプリケーションの文脈で考えているネタです。これ以外のアイディアや手法も色々必要なんですが、まー、シンプルに考えればなんとかなんじゃないのか、と。