はじめに
もうすっかり年末なので、これから2015年にかけてアプリケーションアーキテクチャがどのようになっていくのかという個人的な考え/妄想や背景について、「リアクティブ」というキーワードをもとににまとめてみたいと思います。
Google Trendsを見ると"reactive programming"という言葉は2010年前後から、ゆっくりとバズをし始め、現在も上昇を続けています。
また、仕事としては、2010年ごろから大規模なWebサービス開発において、フロントエンド、バックエンド、アルゴリズム改善といった様々な箇所で、リアクティブプログラミングの要素を取り入れながら、アーキテクチャの改善を進めてきました。そのため、こういったアーキテクチャがコード品質の維持や安定性の向上、実際的で複雑な問題の解決にも適応可能であるということを実感として持っています。
近年、そういった要素が様々なツールやサービスといった形で公開・提供されることが多くなり、より身近になってきたため、2015年にかけてWebアプリケーションのアーキテクチャは「リアクティブ」というキーワードをもとに加速していくのではないかと思っています。
リアクティブネスとは何か
リアクティブと一言で言った時に、現状では2つの含意があります。
1つは、「アーキテクチャの各要素をメッセージ駆動でつなげ、反応的に変化させること」であり、もう1つは「メッセージの送受信を隠蔽し値同士の関係(data-flow)を宣言的(関数型的)に記述するプログラミングパラダイム」です。
前者をリアクティブアプリケーション、後者をリアクティブプログラミングと呼ぶとするならば、リアクティブプログラミングで、リアクティブアプリケーションを記述することができるとシンプルに問題を解決できます。
どちらも非同期なデータフローに注目している点で変わりがないのですが、同じ言葉で語られるため分かりにくさを増大させているように思えます。
リアクティブプログラミングおよびリアクティブアプリケーションについて、概略を捉えたい方は以下の記事がオススメです。
エクセルとリアクティブプログラミング
リアクティブプログラミングについて、説明するときに「Excel」みたいなものだよとよく言われます。以下のシートをみてください。(=は抜いてあります。)
このようなシートがあったときに、A1の値を書き換えたらどのように挙動するでしょうか。
セルの各値は、再計算を指示することなく、値同士の関係性のみを記述しただけで自動的に再更新されます。
次に既存のプログラミングパラダイムで、値同士の関係を記述した場合を考えてみましょう。
var A1 = 1;
var B1 = A1 + 1;
var C1 = B1 + 1;
var A2 = sum(A1,B1,C1);
var B2 = A2 * 2;
このような処理があったときに
A1 = 2;
としても、B1,C1,A2,B2の値は変化せず、A1のみが変化します。
既存のプログラミングパラダイムでA1とB1の関係を記述する場合、observerパターンなどを使い、次のように書く必要があるでしょう。
var A1 = new Cell;
var B1 = new Cell;
A1.on("change",function(e){
B1.setValue(e.value + 1);
});
リアクティブプログラミングを言い換えるのであれば、Observerパターンを意識させず関係性のみを記述することともいえるでしょう。
このようなパラダイムの言語として代表的なものはハードウェア記述言語であるVerilogなどが挙げられるでしょう。電子回路のような物理的パスと信号を取り扱うシステムは、データフローとイベントを取り扱うアーキテクチャと極めて親和性があります。
リスト処理をイベントリスト処理に
では、どのようにしてObserverパターンを隠蔽し、データ同士の関係(data-flow)を記述していくかというと、.NET FrameworkのReactive Extensions(Rx)は、興味深い方法で、この問題に対処し、オブジェクト指向言語にリアクティブプログラミングの要素を追加しました。
その方法とは、上の画像にあるように非同期イベントを時間に対する無限リストとして扱っていくことがそのひとつの答えとなります。
非同期なイベントを時間軸にそって捉えると、イベントのリストとして捉えることができます。
リスト処理であれば、関数型のプログラミングパラダイムに則ってシンプルに問題を記述することができます。
誤解を招く表現かもしれませんが、(関数的)リアクティブプログラミングとはなにかをオブジェクト指向の人に説明するときに「オブザーバをイテレータのように見せることだ」と言ってしまうほうが、分かりやすいかもしれません。イテレータに対する関数を用意することで、ループを隠蔽できるように、オブザーバーのemitするイベントに対する関数を用意することで、複雑なオブザーバ同士の処理を隠蔽します。
以下のブログを見るとpush/pullの関係でそれぞれを捉え直しており理解がしやすいです。IEnumerable vs IObservable
RxJavaも.NETからのポートなので、関係性は同じです。
UNIX哲学とリアクティブネス
UNIX哲学とは、小さく単一責務性のはっきりしたプログラムで標準入出力のストリームを取り扱い、それらを繋げることで複雑なことを為そうという思想です。
【翻訳】パイプとフィルタ ~ソフトウェア工学における有用なアーキテクチャ~
この概念をイベント駆動の世界に持ち込んだ代表例としてはNodejsのStream APIが挙げられます。本質的には、前述のイベントをリスト処理として取り扱う方法と同じなのですが、メタファーが異なり、入出力の概念とそれらをつなぐパイプによって構成されます。
詳しくはjxckさんのNode.js の Stream API で「データの流れ」を扱う方法を参照してください。
MVCとリアクティブネス
http://www.slideshare.net/hirokidaichi/bitter-sweet-javascript-28845287
MVC(Model-View-Controller)というアプリケーションアーキテクチャパターンは、上記のように捉えるとイベントとメッセージによる各レイヤの疎結合化として捉えることができます。
var controller = C(view,userEvent);
var model = M(controller);
var view = V(model);
そのため、リアクティブプログラミングのパラダイムで記述すると、このようにすっきりとした関係で記述することができます。
双方向バインディング
このうち、viewとmodelの関係を複雑なUIの記述から切り離すためにViewModelというデータレイヤーを挟み、UIの記述とViewModelの関係をTemplateによって提供するモデルが登場しました。
こういったアプリケーションパターンをMVVMとよび、ViewModelとUIの関係をつなぐものを双方向バインディングのテンプレートエンジンと呼ぶようになりました。
MVVMと双方向バインディングは、次のような疑似コードで表現することができるでしょう。
var viewModel = BindA(view,userEvent);
var model = M1(viewModel);
viewModel = M2(model);
var view = BindB(viewModel);
angular.jsのバインディング機構やvue.js、knockout.jsなどはこのような双方向バインディング機構を提供しています。この機能は、WebComponentのtemplateの仕組みとも深く関係しています。また、Object.observe
という機能はこういった仕組みを効率的に実装するために不可欠であったため、仕様の策定に先行してGoogle Chromeでの実装が提供されました。
また、React.jsでは基本的な機能としては、BindBに相当するone-wayバインディングとして機能提供しています。(比較的簡単にBindAも実装できる。)
こういったバインディング機構によって、煩雑に肥大化しやすいcontrollerやviewについてほぼ自動的に解決し、viewModelとModelの関係のみに注目することでシンプルにアプリケーションを記述することができます。
また、近年WebComponentなどで注目を集めているModel Driven View(MDV)の考え方もViewとViewModelのバインディングを提供するものだと捉えることができます。
フロントエンドJavaScriptとリアクティブ
フロントエンドJavaScriptとリアクティブプログラミングの関係は、コールバックヘルと切っても切れない関係があります。Ajaxアプリケーションをモジュラーに設計するため、無数のCPS(継続渡しスタイル)のライブラリが2000年代前半には作られていました。
サーバーサイドでの非同期イベント駆動アーキテクチャとして、Twistedのようなフレームワークが登場し、それに触発されてMochikitのDeferredが実装され、JSDeferredとして切り出され、jQuery.Deferredが登場というような流れだったかと記憶しています。
こういったCPSとモナドを組み合わせた非同期フロー制御のライブラリに、EventStreamとしての概念を取り入れたものとして、Flapjax(2009)が登場しました。
このころは、非同期フロー制御のライブラリ百花繚乱といった状況で、クライアント/サーバー問わず様々なライブラリが登場しました。
仮想パネル: JavaScriptで非同期プログラミングを乗り切る方法
IsomorphicでReactiveなアプリケーションフレームワーク
リアクティブアプリケーションを実装する際に難しいのは、クライアントのデータフローとサーバーサイドのデータフローをどのように見せるかという点があげられます。この点を透過的に見せるのはRPCを透過に見せることと同義であるので、非常に難しいです。
一つの方法として、クライアントとサーバーサイドを同一の言語で記述し、ある処理はサーバーサイドで、ある処理はクライアントサイドでと処理系が振り分けていくことです。
Node-WebkitやMeterはそういった方法で、透過的にサーバーサイドとクライアントサイドを切り分けられるようなフレームワークとして登場し、徐々に実用可能なものになりつつあります。
アプリケーションインフラとリアクティブネス
アプリケーションのインフラにとってもリアクティブであることの重要性は高まってきています。それは、Immutable InfrastructureやDisposable Componentとも深く関係しています。
リアクティブマニフェスト
リアクティブなアプリケーションについて、最近version2.0が上梓されたリアクティブマニフェストを読むことで、理解が高まるでしょう。
リアクティブマニフェストで述べられている4つの重要な要素について、説明します。
Responsive:即時応答する
リアクティブなアプリケーションはユーザーのリアクションに対して、即時の応答を返していきます。分かりやすい例はGoogle Docsのような複数人の編集であっても同時にインタラクティブにできるようなものです。あるいはFormボタンでSubmitすることなく、設定が更新できたり、あるいは、今朝のニュースであるのに見ている途中に次に読むべき記事をレコメンドできるようなアプリケーションです。これを実現するには、SPA(Single Page Application)としての実装や、WebSocketやWebRTC、SSEなどの技術が必要でしょうし、クライアントアプリであっても、長時間のコネクションが必要になるでしょう。また、のちに述べるFast Dataの取り扱いも必要となってきます。
Resilient:回復力がある
これは、障害検知というメッセージをもとにその障害の切り離し、および復旧を自動化するということです。これによって、高度なアプリケーションであっても高い可用性を持つことができます。これの実現には、障害監視と復旧スクリプトを結びつけるメッセージシステムが必要でしょう。
Elastic:伸縮自在である
障害に対してだけでなく、負荷に対しても反応する必要があります。負荷の増大や縮小に対して、自動的にスケールアウトしたり、サーバー群を減らしたりすることで、リソースの利用効率を向上させます。障害回復にしても、オートスケールにしても、アプリケーションサーバーが状態をもっているのでは実現は難しくなります。そのため、Immutable Infrastructureの思想が必要となってきます。また、Dockerなどのコンテナ技術も新しいコンテナの起動や削除を高速に行うためには重要な技術となってきます。VMの立ち上げ環境構築では10分以上かかったものがコンテナイメージの立ち上げであれば、30秒になるかもしれません。
そうなってくると負荷予測を行い、30秒後に必要になるコンテナを立ち上げ、30秒後に必要ではないコンテナを下げることができます。ピークタイムや時差をもつアプリケーションにとっては、こういったリソースコントロールはコストメリットが非常に高くなります。
また、アプリケーションサーバーやワーカーなどの要素のスケールアウトを迅速に他のコンポーネントに伝搬させる必要があります。そのためにはserfやZooKeeperのようなメンバーシップ管理とイベント処理を実現する機構が必要になってきます。
Message Driven:メッセージ駆動である
リアクティブなシステムは、このようにコンポーネント間の境界に置いてイベント情報をやり取りするメッセージシステムが必要になります。また、それら自体もElasticで、Resilientである必要があります。
昨日のAWS Re:Inventでは、S3のCRUDをメッセージとして受け取り、SQS経由で利用可能になることが発表されました。このようにクラウド上のサービスもまた、どんどんとメッセージ駆動アーキテクチャの一部として利用可能になっていくでしょう。
また、ログコレクタとストリームプロセッサの役目を果たすfluentdも異なるシステム間のメッセージ処理のために重要なコンポーネントとなりました。
以前、新人プログラマに知っておいてもらいたい人類がオブジェクト指向を手に入れるまでの軌跡
という記事の中で、オブジェクト指向の1つの本質は、メッセージ指向である話をしましたが、これは取り扱う単位が大きくなっても同様と言えるでしょう。複雑なシステム間の相互連携には、仲介するメッセージシステムが必要不可欠です。
CI/CD
リアクティブマニフェストでは、触れられていませんが、継続的インテグレーションや、継続的デリバリーをチャットサービスやGithubのPub/Subと結びつけて、自動化することが近年ではあたりまえになってきています。これもまたリアクティブなアプリケーションの特性であろうと考えています。
AWSでもCodeDeploy,CodeCommit、CodePipelineなどが発表され、git push
というイベントを契機にリアクティブにインフラを駆動させていくことは、ますます身近なものになるでしょう。
ビッグデータとリアクティブネス
リアクティブプログラミングが効果を発揮する場面はGUIやインフラ構築だけではありません。バズワードとしてすっかり定着した感のあるビッグデータの領域にもリアクティブプログラミングの考え方は必須のものとなってきています。
Big Data & Fast Data
ビッグデータといった時に、現在ではHadoopのようなMap/Reduceアーキテクチャの上で、過去のデータに対してのBatch処理を行うというのがスタンダードになっています。
それに対して、もう一つのビッグデータ処理の方法として、Fast Data (Processing)という考え方があります。これは、流れてくるイベントデータに対して、逐次処理しながらリアルタイムに値を更新していくものです。
これはストリームプロセッシングやCEP(Complex Event Processing)とも呼ばれています。それらをサポートするサービスであるAWS Kinesisを利用したガリバーのDRIVE+などの事例も登場しつつあり、面白くなってきそうな分野です。また、AWS Lambdaの登場で、より簡単にEventStreamを取り扱うことができるようになるでしょう。
AWSのプロプラエタリなサービスだけでなく、Stormや、Norikraなどデータストリームに対する処理を行うOSSの基盤が手軽に利用できる時代になっています。
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("words", new TestWordSpout(), 10);
builder.setBolt("exclaim1", new ExclamationBolt(), 3)
.shuffleGrouping("words");
builder.setBolt("exclaim2", new ExclamationBolt(), 2)
.shuffleGrouping("exclaim1");
Twitter Stormでは、イベントソースであるSpoutとそれらをつなぐBoltという概念でリアクティブプログラミングをサポートしています。
手近なところでは、fluentd + Norikraというソリューションがあります。
SELECT COUNT(DISTINCT cookie.id) AS uu,
COUNT(*) AS imps
FROM impressions.win:time_batch(1 hours)
WHERE cookie.valid
Norikraは、esperというSQLでストリーム処理を記述することができるライブラリをベースに開発されたツールで、イベントストリームに対してQueryを投げることでリアルタイムな集計を行うことができます。
Batch処理による数時間、数日単位の変化から分単位のリアルタイムなアプリケーションの改善を初期設計から取り入れていくことが今後増えていくと予想されます。
Lambdaアーキテクチャ
こういった中、バッチ処理とリアルタイム処理を共存させ、その両方を組み合わせていくことで、精度とリアルタイム性のいいとこ取りをするLambdaアーキテクチャが注目されています。
個人的にも2011年くらいにとあるレコメンドの仕組みで、ストリーム処理とバッチ処理を組み合わせるような設計を採用しました。当然、どのような商材を対象とするかによってリアルタイム性と精度をどのように意識するかが違ってきます。
当時は、こういった汎用的な基盤を実サービスで使えるほど枯れていなかったので、自前でそのアルゴリズムを実装していくことになりましたが、現在であれば、Lambdaアーキテクチャにならった基盤を用意し、アルゴリズムのTry & Errorをすることも比較的容易になってきたと言えます。
ストリームアルゴリズムとオンラインアルゴリズム
ストリーム処理では、データが流れてくるごとに有限のメモリをもって、情報をアキュームレーティングしていきます。つまり、過去のデータをすべて参照することはできないという制約が付きます。
このような制約の中で、ある目的を果たすアルゴリズムをストリームアルゴリズムと言います。
ストリームアルゴリズムは、信号処理や通信デバイスの回路設計だけでなく、データサイエンスにとってもますます重要になってくるでしょう。
まとめ
このように「リアクティブ」というキーワードは、GUI、インフラ、ビッグデータ処理など様々な場面で浸透しつつあります。今までは複雑すぎて作ることが難しかったアプリケーションが簡単に設計できるようになっていくでしょう。
一方で、異なるシステム間をうまく繋ぎこみアプリケーションにしていくことはまだまだ難しい状況です。
たとえば、Windows Azureなどのプロプライエタリな環境の中でであれば、LINQなどを使うことでこれらを統一的に取り扱うことができるようになっていくでしょう。
あるいは、AWSがSQS,Lambda,Kinesis,Data Pipelineなどをもっと宣言的に取り扱える仕組みを提供するかもしれませんし、そういった支援を行うツールがOSSのような形で開発されるかもしれません。
はたまた、fluentdのエコシステムが加速することで、こういった環境はダウンサイズされるかもしれません。
とはいえ、こういった流れが進んでいくのは間違いないと思っているので、なんらかの形で関わりたいなと考えている今日この頃です。