reactive programming
TRANSCRIPT
Reactive Programming
@maruyama097丸山不二夫
In a recent post on the NetBeans developer site, one of the core maintainer observed that a single class had been patched over 14 times to fix threading related problems. --- Brian Goetz et al “Java Concurrency In Practice”
1/3 of the code in Adobe’s desktop applications is devoted to event handling logic 1/2 of the bugs reported during a product cycle exist in this code
--- quoted in Martin Odersky’s “Deprecating the Observer Pattern”
Merriam-Webster defines reactive as “readily responsive to a stimulus”, i.e. its components are “active” and always ready to receive events. This definition captures the essence of reactive applications, focusing on systems that: event-driven, scalable, resilient, responsive
--- “Reactive Manifesto”
はじめに Reactive プログラミングの歴史は、インタ
ラクティブな UI の登場とともに古い。表計算ソフトは、もっとも身近な最も成功したReactive プログラミングの一つである。
現代の Reactive プログラミングをドライブしている力の一つは、 Responsive な UI への欲求である。その主要な舞台は、 Web UIである。 Web Component とともに、 post HTML5 の中心的な Web 技術の一つである、Model Driven View(MDV) は、こうしたReactive プログラミング技術と見ることが出来る。
はじめに 現代の Reactive プログラミングをドライブ
しているもう一つの力は、非同期でイベント・ドリブンな処理への志向である。この方向は、 CPU のメニコア化と分散処理の巨大なクラウドへの拡大という、まったく異なる二つの分野でともに強力に押し進められている。
2010 年頃に公開されたマイクロソフト社のRx(Reactive Extention) は、 Observableの役割に注目し、 Reactive プログラミングの新しい手法を切り開いた画期的なものであった。
はじめに Rx は、オープンソース化され、多くの言語
に移植され、また多くの Reactive プログラミング言語に大きな影響を与えた。
Rx の応用で注目すべき事例は、 Netflix による Rx の Java への移植と、それに基づく APIの書き換え・システムの再構築の成功である。
それはまた、小さな関数から大きな関数を合成するという関数型のアプローチの、大規模な実システムでの有効性を示すものでもあった。
Agenda Part I Web と Reactive プログラミング
非同期とイベント・ドリブンへの志向 Web UI と Reactive プログラミング Meteor AngularJS Dart Polymer.dart
Agenda Part II
Reactive Extension の基礎 LINQ と Enumerable Rx と Observable Observable を使った Event 処理 Rx for JavaScript Observable を使った非同期処理 Android と Rx
Agenda Part III
Observable を活用する Observable のストリームを処理する Observable を変形・加工する合成可能な
関数 Server サイドでの Rx の事例 Netflix Netflix での Observable の利用
Agenda Part IV
Reactive Manifesto The Need to Go Reactive Reactive Applications Event-driven Scalable Resilient Responsive Cocclusion
Part I
Web と Reactive プログラミング
表計算と Reactive Web UI
表計算ソフトは Reactive最も身近で、成功したアプリの一つ
表計算のアルゴリズムを考える
Loop { セル Cij への入力 / 修正のイベント検出
Loop { セル Cij への入力 / 修正は終了したか? }
セル Cij に関連付けられている全てのマクロ Mn の検索
Loop { 新しいセル Cij の値で全ての Mn を再計算
Mn が定義されているセル Ckl の値を再表示 }}
AngularJS の処理
AngularJS の処理1. ‘X’ key が押されると、ブラウザーは input コントロールに
keydown イベントを発する。2. input directive は、 input の値の変化を捉えて、 Angular 実
行コンテキスト内のアプリケーション・モデルを変更する為に、 $apply(“name = ‘X’;”) を呼び出す。
3. Angular は、 name = ‘X’; をモデルに適用する。 4. $digest ループが始まる。5. $watch リストは、 name 属性の変化を検出すると、
{{name}} 補完に通知をして、それが、つづいて DOM を更新する。
6. Angular は実行コンテキストを抜け出す。それで、 keydownイベントは終わり、それとともに JavaScript の実行コンテキストも終わる。
7. ブラウザーは、更新されたテキストで、ビューを再描画する。
非同期とイベント・ドリブンへの注目
2009 年 11 月 JSConf
Ryan Dahl の登場 ここでは、 node.js の爆発的な拡大の起点となっ
た、 2009 年ベルリンで行われた、 JSConf でのnode.js の創始者 Ryan Dahl の講演の一部を見てみよう。
“Node.js, Evented I/O for V8 Javascript”
by Ryan Dahl http
://jsconf.eu/2009/speaker/speakers_selected.html#entry-3356
http://s3.amazonaws.com/four.livejournal/20091117/jsconf.pdf
I/O は、違ったやり方で行われる必要がある。 多くの Web アプリは、次のようなコードを
使う。 var result = db.query("select * from T"); // use result データベースを検索している間、ソフトウェ
アは何をしているのだろう? たいていの場合、ただレスポンスを待っているだけである。
それに加えて、 IO による遅延は、コンピュータの能力と比較すると、次のように、巨大なものである。http://s3.amazonaws.com/four.livejournal/20091117/jsconf.pdf
I/O による遅延 L1: 3 cycles L2: 14 cycles RAM: 250 cycles DISK: 41,000,000 cycles NETWORK: 240,000,000 cycles
別の例を挙げよう。サーバーのパフォーマンスも実装によって大きな違いが生まれている。次のズは、 Appache と NGINX を比較したものである。
“Latency as an Effect”
“Principles of Reactive Programming”Coursera lecture by Erik Meijerhttps://class.coursera.org/reactive-001/lecture/51
2013 Nov 4th“Principles of Reactive Programming”
https://www.coursera.org/course/reactive
trait Socket { def readFromMemory(): Array[Byte] def sendToEurope(packet: Array[Byte]): Array[Byte]}
val socket = Socket()val packet = socket.readFromMemory()val confirmation = socket.sendToEurope(packet)
PC での処理時間
シーケンシャルな実行時間
val socket = Socket()val packet = socket.readFromMemory() // 50,000 ナノ秒ブロックする // 例外が発生しない時には、次の処理へval confirmation = socket.sendToEurope(packet) // 150,000,000 ナノ秒ブロックする // 例外が発生しない時には、次の処理へ
1 ナノ秒を 1 秒だと思うと
1ナノ秒を 1秒だとした場合の実行時間
val socket = Socket()val packet = socket.readFromMemory() // 3日間ブロックする // 例外が発生しない時には、次の処理へval confirmation = socket.sendToEurope(packet) // 5 年間ブロックする // 例外が発生しない時には、次の処理へ
このようなコードは、全体のプロセスをブロックするか、複数のプロセスの実行をスタックさせることになるかのいずれかである。
しかし、次のようなコードでは、プログラムは、すぐに event loop に戻ることが可能である。どのようなメカニズムも必要としない。
これが、 I/O が行われるべき方法である。 では、何故、誰もが event loop や Callback
や Non-BlokingI/O を使わないのだろうか?
db.query("select..", function (result) { // use result});
var result = db.query("select..");
文化的偏見 我々は、 I/O を次のように行うと教えられて
きた puts("Enter your name: "); var name = gets(); puts("Name: " + name);
我々は、入力を要求されたら、それが終わるまで何もしないと教えられてきた。次のようなコードは、複雑すぎるとして、退けられてきた。 puts("Enter your name: "); gets(function (name) { puts("Name: " + name); });
インフラの不在 それでは、何故、誰もが event loop を使わ
ないのだろう? シングル・スレッドの event loop は、 I/O
が non-blocking であることを要求するのだが、ほとんどのライブラリーはそうなっていない。 POSIX async file I/O は使えない . Man pages は、関数がディスクにアクセスする
ことがあることを触れていない。 (e.g getpwuid())
C に、クロージャーや無名関数がないことが、コールバックを難しくしている。
データベースのライブラリーは (e.g. libmysql) 、非同期のサポートをしていない。
JavaScript
Javascript は、 event loop と一緒に使われるように、特別にデザインされた言語である。 無名関数、クロージャー 同時には、一つだけのコールバック DOM のイベントコールバックを通じた I/O
JavaScript のカルチャーは、既に、イベント中心のプログラミングの準備ができている。
node.js project:
高度な並列プログラムを記述する、純粋にイベント中心で、 non-blocking なインフラを提供すること。
Libuv
libuv enforces an asynchronous, event-driven style of programming.
Its core job is to provide an event loop and callback based notifications of I/O and other activities.
libuv offers core utilities like timers, non-blocking networking support, asynchronous file system access, child processes and more.
“Brief Hisory of Node.js”Ben Noordhuis
http://vimeo.com/51637038
“An Introduction to libuv”Nikhil Marathe
November 10, 2013 Release 1.0.0http://nikhilm.github.io/uvbook/An%20Introduction%20to%20libuv.pdf
#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <uv.h>
void on_read(uv_fs_t *req);
uv_fs_t open_req;uv_fs_t read_req;uv_fs_t write_req;
char buffer[1024];
void on_write(uv_fs_t *req) { // callback の signiture uv_fs_req_cleanup(req); if (req->result < 0) { fprintf(stderr, "Write error: %s\n", uv_strerror(uv_last_error(uv_default_loop()))); } else { uv_fs_read(uv_default_loop(), &read_req, open_req.result, buffer, sizeof(buffer), -1, on_read); // callback }} // on_write callback の中で、 on_read callback が呼ばれる
libuv スタイルでのcat の実装
open, red, write のlibuv版は、第一引数にloop を取る。最終引数はcallback
イベントのようなもの
void on_read(uv_fs_t *req) { // callback の signiture uv_fs_req_cleanup(req); if (req->result < 0) { fprintf(stderr, "Read error: %s\n", uv_strerror(uv_last_error(uv_default_loop()))); } else if (req->result == 0) { uv_fs_t close_req; // synchronous uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL); } // callback が NULL 、同期型 else { uv_fs_write(uv_default_loop(), &write_req, 1, buffer, req->result, -1, on_write); } // callback} // on_read callback の中で、 on_write callback が呼ばれる
void on_open(uv_fs_t *req) { // callback の signiture if (req->result != -1) { uv_fs_read(uv_default_loop(), &read_req, req->result, buffer, sizeof(buffer), -1, on_read); // callback } else { fprintf(stderr, "error opening file: %d\n", req->errorno); } uv_fs_req_cleanup(req);} // on_open callback の中で、 on_read callback が呼ばれる
int main(int argc, char **argv) { uv_fs_open(uv_default_loop(), &open_req, argv[1], O_RDONLY, 0, on_open); // callback return 0;} // open は、 on_open callback を呼んで、すぐループに入る。
Callback Hell
“A guide to writing asynchronous javascript programs” http://callbackhell.com/Asynchronous javascript, or javascript that uses callbacks, is hard to get right intuitively.
“Callback hell in nodejs?”http://stackoverflow.com/questions/18095107/callback-hell-in-nodejsIn below code am I in callbackhell? How to overcome such scenario without using any async modules in pure javascript?
Web UI と Reactive プログラミング
Web UI での Reactive / Model Driven View(MDV) への志向 Meteor.js
Reactive プログラミング Reactive-context と Reactive-data-source publish/subscribe モデル
AngularJS Imperative から declarative Data binding
Polymer.js Model Driven View
.....
MeteorReactive プログラミング
http://docs.meteor.com/https://github.com/meteor/meteor
<template name="hello"> <div class="greeting">Hello there, {{first}} {{last}}!</div></template>
// in the JavaScript console> Template.hello({first: "Alyssa", last: "Hacker"}); => "<div class="greeting">Hello there, Alyssa Hacker!</div>"
Meteor.render(function () { return Template.hello({first: "Alyssa", last: "Hacker"});}) => automatically updating DOM elements
Meteor Template
var fragment = Meteor.render( function () { var name = Session.get("name") || "Anonymous"; return "<div>Hello, " + name + "</div>"; });document.body.appendChild(fragment);
Session.set(“name”, “Bob”); // ページは自動的に更新される
Meteor LiveHTML
Meteor.render() Meteor.render は、 rendering function 、ある
HTML を文字列として返すを関数を引数に取る。それは、自動更新する DocumentFragment を返す。
rendering function で利用されたデータに変更があったとき、それは、再実行される。 DocumentFragment 内の DOMノードは、ページのどの場所に挿入されていても、その場で自分自身を更新する。それは全く自動的である。
Meteor.render は、 rendering function によって、どのデータが利用されたかを発見する為にreactive context を使う。
Meteor: reactive context + reactive data source
reactive context + reactive data source という単純なパターンは、広い応用可能性をもっている。
それ以上に、プログラマーは、 unsubscribe/resubscribe の呼び出しのコードを書く手間が省け、それらは、正しい時に確実に呼び出されことになる
一般的に、 Meteor は、そうでなければ、誤りを犯しやすいロジックで、アプリケーションの邪魔になる、データ伝搬の全てのクラスを取り除くことが出来る、
AngularJSData Binding
http://angularjs.org/https://github.com/angular/angular.js
AngularJSData Binding デモ
<!doctype html><html ng-app> <head> <script src="http://code.angularjs.org/1.2.0rc1/angular.min.js"> </script> </head> <body> <input ng-model="name"> <p>Hello {{name}}!</p> </body></html>
Controller
controller は、 view の背後にあるコードである。その役割は、モデルを構築して、コールバック・メソッドにそって、それをビューに公開することである。
view は、 scope の template( HTML)上への投射である。
scope は、モデルを view につなげ、イベントを Controller に向けさせる「つなぎ」の役割を果たす。
The separation of the controller and the view controller は、 JavaScript で書かれる。
JavaScript は命令型である。命令型は、アプリケーションの振る舞いを指定するには、もっとも適している。 controller は、( DOM への参照、 HTML の断片のような)レンダー用の情報を含むべきではない。
view template は HTML で書かれる。 HTML は宣言型である。宣言型は、 UI の指定に最適である。 view は、振る舞いを含むべきではない。
controller は view のことを知らないので、同一のcontoroller に対して複数の view があり得る。このことは、スキンの変更・デバイス特有の view(モバイルと PC)・テスト可能性に取って、重要である。
Model model は、 template とマージされて view を生み
出すデータである。 model が view にレンダーされる為には、 model は、 scope から参照されなければならない。
他の多くのフレームワークとは異なって、 Angularは、 model について何の制限も要請も設けてはいない。 model にアクセス、あるいは model を変更する為に、継承すべきクラスも、特別のアクセス・メソッドもない。
model は、プリミティブでも、オブジェクト・ハッシュでも、全くのオブジェクト型でも構わない。要するに、 model は、” plain JavaScript object” なのである。
View
view は、ユーザーが見るものである。 viewのライフサイクルは、 template として始まる。 view は、 model とマージされて、最終的には、ブラウザーの DOM にレンダーされる。
Angular は、他の大部分の template システムと比較して、全く異なるアプローチを採用している。
Angular の template システムは、文字列の上ではなく DOM オブジェクトの上で動く。template は確かに HTML で書かれているが、それは HTML である。
MDV templates in Dart
https://www.dartlang.org/articles/web-ui/#one-way-binding
MDV templates in Dart
One-way data binding: Embed data into your UI
Two-way data binding: Keep data in sync with UI changes
Conditionals: Selectively show parts of the UI
Loops: Construct lists and tables by iterating over collections
Event listeners: Easily attach code that reacts to UI events
One-way data binding<html lang="en"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <link rel="stylesheet” href="//cdnjs.cloudflare.com/ajax/libs /twitter-bootstrap/2.3.1/css/bootstrap.css"></head><body> <div class="well">Hello {{dataValue}}!</div> <script type="application/dart"> String dataValue; main() { var today = new DateTime.now(); dataValue = 'world ${today.year}-${today.month}-${today.day}'; } </script></body></html>
<html lang="en"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <link rel="stylesheet ”href="//cdnjs.cloudflare.com/ajax/libs /twitter-bootstrap/2.3.1/css/bootstrap.css"></head><body> <div class="well">Hello counter: {{count}}</div> <script type="application/dart"> import 'dart:async' as async; import 'dart:html' as html; import 'package:web_ui/watcher.dart' as watchers; int count; main() { count = 0; new async.Timer.periodic(const Duration(seconds: 1), (_) { count++; watchers.dispatch(); }); } </script></body></html>
Two-way data binding<html lang="en"><head> .....</head><body> <div class="well"> Input: <input type="text" bind-value="str" placeholder="type something here"> <div> Value: {{str}}</div> <div> Length: {{str.length}}</div> </div> <script type="application/dart"> String str = ''; main() {} </script></body></html>
<html lang="en"><head> .....</head><body> <div class="well"> <div> Input1: <input type="text" bind-value="str1"></div> <div> Input2: <input type="text" bind-value="str2"></div> <template if='str1 == str2'> <div>They match!</div> </template> </div> <script type="application/dart"> String str1 = ''; String str2 = ''; main() {} </script></body></html>
Conditionals
<html lang="en"><head> .....</head><body> <input type="checkbox" bind-checked="show">Show phones <br> <table class="table"> <thead> <tr> <td>Last</td><td>First</td> <td template if="show">Phone</td> </tr> </thead> <tbody> <tr> <td>Bracha</td> <td>Gilad</td> <td template if="show">555-555-5555</td> </tr>
<tr> <td>Bak</td> <td>Lars</td> <td template if="show">222-222-2222</td> </tr> <tr> <td>Ladd</td> <td>Seth</td> <td template if="show">111-222-3333</td> </tr> </tbody></table> <script type="application/dart"> bool show = true; main() {} </script></body></html>
Loops
<html lang="en"><head> .....</head><body> <table class="table"> <tbody template iterate='row in table'> <tr template iterate='cell in row'> <td>{{cell}}</td> </tr> </tbody> </table> <script type="application/dart"> var table = [['X', 'O', '_'], ['O', 'X', '_'], ['_', '_', 'X']]; main() {} </script></body></html>
Event listeners
<html lang="en"><head> .....</head><body> <div class="well"> <button on-click="increment()">Click me</button> <span>(click count: {{count}})</span> </div> <script type="application/dart"> int count = 0; void increment() { count++; } main() {} </script></body></html>
Polymer.dartObservables and Data Binding with Web UI
https://www.dartlang.org/web-ui/observables/
Overview of data binding in Web UI
Web UI helps you efficiently bind application data to HTML, and vice versa, with observables and observers.
Observables are variables, fields, or collections that can be observed for changes.
Observers are functions that run when an observable changes.
Efficiently tracking changes
Instead of asking every possible observable “Did you change?” on every event loop over and over, Web UI has an efficient mechanism to notify only the right observers at the right time.
Observing variables
<body> <h1>Hello Web UI</h1> <p>Web UI is {{ superlative }}</p> <button id="change-it" on-click="changeIt()"> Change</button> <script type="text/javascript" src="dart.js”> </script> <script type="application/dart" src="hello_world.dart"> </script> </body>
library hello_world;import 'package:web_ui/web_ui.dart';
@observableString superlative = 'awesome';
const List<String> alternatives = const <String>['wicked cool', 'sweet', 'fantastic', 'wonderful'];int _alternativeCount = 0;String get nextAlternative => alternatives[_alternativeCount++ % alternatives.length];changeIt() { superlative = nextAlternative;}
main() { }
Observing classes<p>Hello {{person.name}}!</p><p><button on-click="newName()"> Change Name</button></p>
@observableclass Person { String name; Person(this.name);}final Person person = new Person('Bob');const List<String> names = const <String> ['Sally', 'Alice', 'Steph'];newName() { person.name = nextName;}
Observing collections
final List<DateTime> timestamps = toObservable([]);
void addTimestamp() { timestamps.add(new DateTime.now());}
void clear() { timestamps.clear();}
Observing nested objects
@observableclass Person { String name; Address address;}
@observableclass Address { String city;}
@observablePerson person;
main() { person = new Person() ..name = 'Clark Kent' ..address = ( new Address() ..city = 'Metropolis' );}
Expression Observers<p>The time is <span id="msg"></span></p><p><button on-click="updateMsg()"> Update</button></p>@observableString msg;
updateMsg() { msg = new DateTime.now().toString();}
main() { observe(() => msg, (_) { query('#msg').text = msg; });}
Part II
Reactive Extension の基礎
Reactive Extension (Rx) by Microsoft
http://msdn.microsoft.com/en-us/data/gg577609.aspx
Reactive Extension とは? “The Reactive Extensions (Rx) is a
library to compose asynchronous and event-based programs using observable collections and LINQ-style query operators.”
「 Reactive Extension は、非同期かつイベント・ベースのプログラムをオブザーバブル・コレクションと LINQ スタイルの検索操作を利用して構成する為のライブラリーである。」
講演資料 2010 年 11 月 DevCamp Keynote
“Rx: Curing your asynchronous programming blues”http://channel9.msdn.com/Blogs/codefest/DC2010T0100-Keynote-Rx-curing-your-asynchronous-programming-blues
2012 年 6 月 TechEd Europe “Rx: Curing your asynchronous programming blues”http://channel9.msdn.com/Events/TechEd/Europe/2012/DEV413
Rx オープンソース化と移植 2012 年 11 月 MS Open Tech からオープン
ソースとして公開。Rx.NET :https://github.com/Reactive-Extensions/Rx.NET
あわせて、 JavaScript 、 C++ 、 Python 、 Ruby への移植版もオープンソースとして公開。 RxJS : https://github.com/Reactive-Extensions/RxJS
RxCpp :https://github.com/Reactive-Extensions/RxCpp
RxPy : https://github.com/Reactive-Extensions/RxPy
Rx.rb : https://github.com/Reactive-Extensions/Rx.rb
RxJava
Netflix は、 Rx.NET を Java に移植し、オープンソースとして公開した。RxJava: https://github.com/Netflix/RxJava/
Netflix は、自社のシステムを RxJava を用いて書き換え、大規模システムでの Reactive プログラミングの有効性を示した。
2013 年 2 月 “ Functional Reactive in the Netflix API with RxJava”http://techblog.netflix.com/2013/02/rxjava-netflix-api.html
RxJava の移植によって、 JVM上で走る、 Clojure, Scala, Groovy, Jruby等の言語でも Reactive Extension が走るようになった。
LINQ と Enumerable
データソースの抽象化として、 .NET 3.0から導入された LINQ で、中心的な役割を果たすのは、 Enumerable インターフェースである。http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
interface IEnumerable<out T>{ IEnumerator<T> Enumerator();}
interface IEnumerator<out T> : IDisposable{ bool Next(); T Current { get; } void Reset();}
LINQ の最も重要なインターフェースEnumerable – pull-based
全ての Collection クラスは、 IEnumerable を実装している
LINQ で利用出来るオペレータ
Projection: Select, SelectManyRestriction: WherePartitioning: Take, Skip, TakeWhile, ...Ordering: OrderBy, OrderByDecsending, ...Grouping: GroupBySet: Distinct, Union, Intersect, ..Conversion: ToList, ToArray, ..Generation: Range, RepeatAggregate: Count, Sum, Min, Max, Average,....
public void Linq1() { int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var lowNums = from n in numbers where n < 5 select n; Console.WriteLine("Numbers < 5:"); foreach (var x in lowNums) { Console.WriteLine(x); } }
LINQ は、 SQL ライクな検索構文で、データソースからデータソースを導出することが出来る
public void Linq5() { string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var shortDigits = digits.Where( (digit, index) => digit.Length < index); Console.WriteLine("Short digits:"); foreach (var d in shortDigits) { Console.WriteLine( "The word {0} is shorter than its value.", d); } }
LINQ では、検索式の中にラムダ式を利用出来る
public void Linq14() { int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 }; int[] numbersB = { 1, 3, 5, 7, 8 }; var pairs = from a in numbersA from b in numbersB where a < b select new { a, b }; Console.WriteLine("Pairs where a < b:"); foreach (var pair in pairs) { Console.WriteLine( "{0} is less than {1}", pair.a, pair.b); } }
SelectMany (多段の Select)
public void Linq15() { List<Customer> customers = GetCustomerList();
var orders = from c in customers from o in c.Orders where o.Total < 500.00M select new { c.CustomerID, o.OrderID, o.Total }; ObjectDumper.Write(orders); }
SelectMany (多段の Select)
public void Linq18() { List<Customer> customers = GetCustomerList(); DateTime cutoffDate = new DateTime(1997, 1, 1);
var orders = from c in customers where c.Region == "WA" from o in c.Orders where o.OrderDate >= cutoffDate select new { c.CustomerID, o.OrderID }; ObjectDumper.Write(orders); }
SelectMany (多段の Select)
public void Linq41() { string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese" }; var wordGroups = from w in words group w by w[0] into g select new { FirstLetter = g.Key, Words = g }; foreach (var g in wordGroups) { Console.WriteLine( "Words that start with the letter '{0}':", g.FirstLetter); foreach (var w in g.Words) { Console.WriteLine(w); } } }
Words that start with the letter 'b':blueberrybananaWords that start with the letter 'c':chimpanzeecheeseWords that start with the letter 'a':abacusapple
Reactive Extension とObservable
Rx で中心的な役割を果たすのは、イベントのソースを抽象化した Observable である。それは、データソースの抽象化として、 LINQ で中心的な役割を果たすEnumerable とよく似ている。
Observable と Observer
Observable は、 Enumerable と同様に、データの集まり。それは相互に変換可能。
Observable には時間の概念が入っている。それは時間とともにデータを生み出す。イベントの抽象に用いられる時に便利である。
データ・ソースとしての Observable が生成するデータは、 Observable に Observerが Subscribe した時に、はじめて利用出来る。
データは Observable から Subscribe の時に指定した Observer上の関数に自動的に送り出される。( Push)
Observer上の三つの関数OnNext, OnError, OnComplete
Observable から Observer へデータがpush される時に、次の三つの関数のみが利用される。 OnNext(): 通常のデータの受け渡し。 OnError(): エラーが発生したこと伝える。 OnComplete(): 送るべきデータが終了したこ
とを伝える。 これらの関数の定義は、 Observable への
Subscribe の際に与えられねばならない。 Observable への Subscribe
は、 Subscription を返す。この値は、 Sunscribe をやめる時に利用される。
Enumerable
Observable
Enumarable と Observable
時間の流れ
どちらもデータの集りである
Enumerable と Observable は、相互に変換出来る
Enumerable Observable// Introduces concurrency to enumerate and signal…var xs = Enumerable.Range(0, 10).ToObservable();
Observable Enumerable// Removes concurrency by observing and yielding…var ys = Observable.Range(0, 10).ToEnumerable();
interface IObservable<out T>{ IDisposable Subscribe(IObserver<T> observer);}
interface IObserver<in T>{ void OnNext(T value); void OnError(Exception ex); void OnCompleted();}
Rx の最も重要なインターフェースObservable push-based
Observer が実装する3つのタイプのメソッド
Observable から Observer への通知
OnNext(42)
Observablesource1
OnNext(43)
OnCompleted
OnNext(“Hello”)
Observablesource2
OnError(error)
OnNext* (OnError | OnCompleted)?
// Range() は、次のような Observabl を生成する// IObservable<int> source = Observable.Range(1, 10);
// ここでは、 Subscribe() は、三つの引数を取っている。// 順番に、 OnNext,OnError,OnComplete の定義であるIDisposable subscription = source.Subscribe( x => Console.WriteLine("OnNext: {0}", x), ex => Console.WriteLine("OnError: {0}", ex.Message), () => Console.WriteLine("OnCompleted"));
Console.WriteLine("Press ENTER to unsubscribe...");Console.ReadLine();// Subscribe をやめるsubscription.Dispose();
Observer が実装する3つのタイプのメソッド
-1-2-3-4-5-6-7-8-9-10-|
internal class Generate_Simple{ private static void Main() { var observable = Observable.Generate(1, // 初期値 x => x < 6, // 条件 x => x + 1, // 増分 x => x, // 返り値 x=>TimeSpan.FromSeconds(1)).Timestamp();
// ここでは、 Subscribe には OnNext の定義だけが与えられている using (observable.Subscribe(x => Console.WriteLine( "{0}, {1}", x.Value, x.Timestamp))) { Console.WriteLine("Press any key to unsubscribe"); Console.ReadKey(); }
Console.WriteLine("Press any key to exit"); Console.ReadKey(); }}
-1-2-3-4-5-|
class Select_Simple{ static void Main() { // 一秒間隔で数列を生成する var oneNumberPerSecond = Observable. Interval(TimeSpan.FromSeconds(1)); var numbersTimesTwo = // 新しい Observable の生成 from n in oneNumberPerSecond select n * 2; // LINQ が使える!
Console.WriteLine("Numbers * 2:"); numbersTimesTwo.Subscribe(num => { Console.WriteLine(num); } // OnNext の定義が引数で与えられている ); Console.ReadKey(); }}
LINQ と RxEnumerable と Observable
Environment
MoveN
ex
tGot next?
Application
On
Next
Have next!
IEnumerable<T>IEnumerator<T>
IObservable<T>IObserver<T>
Inte
racti
ve R
eactiv
e
Observable を使った Event 処理
var lbl = new Label(); var frm = new Form { Controls = { lbl } };
var moves = Observable. FromEvent<MouseEventArgs>(frm, "MouseMove"); using (moves.Subscribe (evt => { lbl.Text = evt.EventArgs.Location.ToString(); })) { Application.Run(frm); }
マウスイベントを Observable にするマウスは、イベントのデータベース!
var txt = new TextBox(); var frm = new Form { Controls = { txt } };
var moves = Observable.FromEvent<MouseEventArgs> (frm, "MouseMove"); var input = Observable.FromEvent<EventArgs> (txt, "TextChanged");
var movesSubscription = moves.Subscribe( evt => { Console.WriteLine("Mouse at: " + evt.EventArgs.Location); }); var inputSubscription = input.Subscribe( evt => { Console.WriteLine("User wrote: " + ((TextBox)evt.Sender).Text); });
using (new CompositeDisposable( movesSubscription, inputSubscription)) { Application.Run(frm); }
var moves = from evt in Observable. FromEvent<MouseEventArgs>(frm, "MouseMove") select evt.EventArgs.Location; var input = from evt in Observable. FromEvent<EventArgs>(txt, "TextChanged") select ((TextBox)evt.Sender).Text;
var movesSubscription = moves.Subscribe( pos => Console.WriteLine("Mouse at: " + pos)); var inputSubscription = input.Subscribe( inp => Console.WriteLine("User wrote: " + inp));
var overFirstBisector = from pos in moves where pos.X == pos.Y select pos; var movesSubscription = overFirstBisector.Subscribe( pos => Console.WriteLine("Mouse at: " + pos));
var input = (from evt in Observable.FromEvent<EventArgs>(txt, "TextChanged") select ((TextBox)evt.Sender).Text) .Do(inp => Console.WriteLine("Before DistinctUntilChanged: " + inp)) .DistinctUntilChanged();
var input = (from evt in Observable.FromEvent<EventArgs>(txt, "TextChanged") select ((TextBox)evt.Sender).Text) .Throttle(TimeSpan.FromSeconds(1)) .DistinctUntilChanged();
var input = (from evt in Observable.FromEvent<EventArgs>(txt, "TextChanged") select ((TextBox)evt.Sender).Text) .DistinctUntilChanged();
using (input.ObserveOn(lbl).Subscribe(inp => lbl.Text = inp)) Application.Run(frm);
違った値が来た時のみデータを送る
Do は、 Observab l を変化させずに、 OnNext を実行する
Throttle は指定された時間の間、 Observable のイベントを抑制するTimeSpan.FromSecond(1) は、一秒間の指定
Reactive Extension for JavaScript
var source = null; // Observable として source を生成するコード(省略)
var subscription = source.Subscribe( // 三つの関数の定義が順番に引数に与えられている function (next) { $("<p/>").html("OnNext: " + next) .appendTo("#content"); }, function (exn) { $("<p/>").html("OnError: " + exn) .appendTo("#content"); }, function () { $("<p/>").html("OnCompleted") .appendTo("#content"); });
var source = Rx.Observable.Empty();
OnCompleted
var source = Rx.Observable.Throw("Oops!");
OnError: Oops
var source = Rx.Observable.Return(42);
OnNext: 42 OnCompleted
var source = Rx.Observable.Range(5,3);
OnNext: 5 OnNext: 6 OnNext: 7 OnCompleted
var source = Rx.Observable.Generate( 0, function (i) { return i < 4; }, function (i) { return i + 1; }, // Like a for loop function (i) { return i * i; });
OnNext: 0 OnNext: 1 OnNext: 4 OnNext: 9 OnCompleted
RxJS:DOM イベントを Observableに
$(document).ready(function () { $(document).mousemove(function (event) { // A position tracking mechanism, // updating a paragraph called “content” $("<p/>") .text("X: " + event.pageX + " Y: " + event.pageY) .appendTo("#content"); }); });
$(document).ready(function () { $(document).toObservable("mousemove") .Subscribe(function (event) { // A position tracking mechanism, // updating a paragraph called “content” $("<p/>") .text("X: " + event.pageX + " Y: " + event.pageY) .appendTo("#content"); }); });
jQuery
RxJS
$(document).ready(function () { $(document).toObservable("mousemove") .Subscribe(function (event) { $("<p/>") .text("X: " + event.pageX + " Y: " + event.pageY) .appendTo("#content"); });
$("#textbox").toObservable("keyup") .Subscribe(function (event) { $("<p/>") .text("User wrote: " + $(event.target).val()) .appendTo("#content"); }); });
var moves = $(document).toObservable("mousemove") .Select(function(event) { return { pageX : event.pageX, pageY : event.pageY }; });
var inputs = $(“#textbox”).toObservable(”keyup”) .Select(function(event) { return $(event.target).val(); });
var movesSubscription = moves.Subscribe( function (pos) { $("<p/>”) .text("X: " + pos.pageX + " Y: " + pos.pageY) .appendTo("#content"); });
var inputSubscription = input.Subscribe( function (text) { $("<p/>") .text("User wrote: " +text) .appendTo("#content"); });
var overFirstBisector = moves .Where(function(pos) { return pos.pageX === pos.pageY; });
var movesSubscription = overFirstBisector .Subscribe(function (pos) { $("<p/>") .text("Mouse at: "+pos.pageX+","+pos.pageY) .appendTo("#content"); });
var inputs = $(“#textbox”).toObservable(”keyup”) .Select(function(event) { return $(event.target).val(); } .DistinctUntilChanged(); );
var inputSubscription = input.Subscribe( function (text) { $("<p/>") .text("User wrote: " +text) .appendTo("#content"); });
var inputs = $(“#textbox”).toObservable(”keyup”) .Select(function(event) { return $(event.target).val(); } .Throttle(1000) .DistinctUntilChanged(); );
var inputSubscription = input.Subscribe( function (text) { $("<p/>") .text("User wrote: " +text) .appendTo("#content"); });
Observable と非同期処理
FileStream fs = File.OpenRead("data.txt");byte[] bs = new byte[1024];fs.BeginRead(bs, 0, bs.Length, new AsyncCallback(iar => { int bytesRead = fs.EndRead(iar); // Do something with bs[0..bytesRead-1] }), null);
.NET の非同期プログラミング
FileStream fs = File.OpenRead("data.txt");Func<byte[], int, int, IObservable<int>> read =
Observable.FromAsyncPattern<byte[], int, int, int>( fs.BeginRead, fs.EndRead);
byte[] bs = new byte[1024];read(bs, 0, bs.Length).Subscribe(bytesRead => { // Do something with bs[0..bytesRead-1]});
Rx での非同期処理
非同期サンプルネットワーク上のサービスの利用
ReactTextChanged Dictionary
web service
Asynchronous request
ReactionReactiveReactor
IObservable<string>
IObservable<DictionaryWord[]>
Data binding
on UI thread
$$$
// IObservable<string> from TextChanged eventsvar changed = Observable.FromEvent<EventArgs>(txt,"TextChanged");var input = (from text in changed select ((TextBox)text.Sender).Text); .DistinctUntilChanged() .Throttle(TimeSpan.FromSeconds(1));
// Bridge with the dictionary web servicevar svc = new DictServiceSoapClient();var lookup = Observable.FromAsyncPattern<string,DictionaryWord[]> (svc.BeginLookup, svc.EndLookup);
// Compose both sources using SelectManyvar res = from term in input from words in lookup(term) select words;
function searchWikipedia(term) { return $.ajaxAsObservable( { url: "http://en.wikipedia.org/w/api.php", dataType: "jsonp", data: { action: "opensearch", search: term, format: "json" } }) .Select(function (d) { return d.data[1]; }); } var searchObservable = searchWikipedia("react"); var searchSubscription = searchObservable.Subscribe( function (results) { $("#results").empty(); $.each(results, function (_, result) { $("#results").append("<li>" + result + "</li>"); }); }, function (exn) { $("#error").text(error); } );
var searchObservable = terms.SelectMany( function (term) { return searchWikipedia(term); } );
var searchObservable = terms.SelectMany(searchWikipedia);
var res = from term in terms from words in searchWikipedia(term) select words;
Functional Reactive Programming on Android With RxJava
http://mttkay.github.io/blog/2013/08/25/functional-reactive-programming-on-android-with-rxjava/
class DownloadTask extends AsyncTask<String, Void, File> {
protected File doInBackground(String... args) { final String url = args[0]; try { byte[] fileContent = downloadFile(url); File file = writeToFile(fileContent); return file; } catch (Exception e) { // ??? } }
protected void onPostExecute(File file) { Context context = getContext(); // ??? Toast.makeText(context, "Downloaded: " + file.getAbsolutePath(), Toast.LENGTH_SHORT) .show(); }}
現在の AsyncTask を使った処理
private Observable<File> downloadFileObservable() { return Observable.create(new OnSubscribeFunc<File>() { @Override public Subscription onSubscribe( Observer<? super File> fileObserver) { try { byte[] fileContent = downloadFile(); File file = writeToFile(fileContent); fileObserver.onNext(file); fileObserver.onCompleted(); } catch (Exception e) { fileObserver.onError(e); } return Subscriptions.empty(); } });}
class MyFragment extends Fragment implements Observer<File> { private Subscription subscription;
@Override
protected void onCreate( Bundle savedInstanceState) { subscription = AndroidObservables .fromFragment(this,downloadFileObservable()) .subscribeOn(Schedulers.newThread()) .subscribe(this); }
private Observable<File> downloadFileObservable() { /* as above */ }
@Override protected void onDestroy() { subscription.unsubscribe(); }
Observable<String> filePathObservable = downloadFileObservable().map(new Func1<File, String>() { @Override public String call(File file) { return file.getAbsolutePath(); }});
// now emits file paths, not `File`ssubscription = filePathObservable.subscribe( /* Observer<String> */);
public void onNext(File file) { Toast.makeText(getActivity(), "Downloaded: " + file.getAbsolutePath(), Toast.LENGTH_SHORT) .show(); }
public void onCompleted() {}
public void onError(Throwable error) { Toast.makeText(getActivity(), "Download failed: " + error.getMessage(), Toast.LENGTH_SHORT) .show(); }}
RxJava on Android with Scala => AWESOME
http://pommedeterresautee.blogspot.jp/2013/11/rxjava-on-android-with-scala-awesome.html
def getAsyncUrl(urlString: String) :Observable[Option[String]] = Observable { (observer: Observer[Option[String]]) => { val url = new URL(urlString) val urlCon = url.openConnection() urlCon.setConnectTimeout(5000) urlCon.setReadTimeout(5000) val io = Source.fromInputStream( urlCon.getInputStream) val result = Option(io.getLines(). mkString.split("\n")(0)) io.close() observer.onNext(result) observer.onCompleted() Subscription() } }
val mUnsubscribe = CompositeSubscription() mUnsubscribe += getAsyncUrl(lastScript) .execAsync.subscribe( _ match { case OnNext(Some(t)) => CInfo(t) case OnNext(None) => CAlert(getString( R.string.connection_error_message, "Server error")) case OnError(e) => CAlert(getString(R.string.connection_ error_message, e.getLocalizedMessage)) })
Reactive extensions for .NET (Rx .NET) for Windows Phone
http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff431792%28v=vs.105%29.aspx
Part III
Observable を活用する
Observable のストリームを処理する Rx サンプル
Stock Trade AnalysisMSFT27.01
ticks
INTC21.75
MSFT27.96
MSFT31.21
INTC22.54
INTC20.98
MSFT30.73
from tick in ticks
MSFT27.01
ticks
INTC21.75
MSFT27.96
MSFT31.21
INTC22.54
INTC20.98
MSFT30.73
27.01
27.96
31.21
30.73
MSFT
21.75
22.54
20.98
INTC
from tick in ticksgroup tick by tick.Symbol
Stock Trade Analysis
MSFT27.01
ticks
INTC21.75
MSFT27.96
MSFT31.21
INTC22.54
INTC20.98
MSFT30.73
MSFT
INTC
from tick in ticksgroup tick by tick.Symbol into companyfrom openClose in company.Buffer(2, 1)
[27.01, 27.96]
[27.96, 31.21]
[31.21, 30.73]
[21.75, 22.54]
[22.54, 20.98]
Stock Trade Analysis
Stock Trade AnalysisMSFT27.01
ticks
INTC21.75
MSFT27.96
MSFT31.21
INTC22.54
INTC20.98
MSFT30.73
MSFT
INTC
from tick in ticksgroup tick by tick.Symbol into companyfrom openClose in company.Buffer(2, 1)
let diff = (openClose[1] – openClose[0])/openClose[0]
0.034
0.104
-0.01
5
0.036
-0.06
9
Stock Trade Analysis
MSFT27.01
ticks
INTC21.75
MSFT27.96
MSFT31.21
INTC22.54
INTC20.98
MSFT30.73
MSFT
INTC
from tick in ticksgroup tick by tick.Symbol into companyfrom openClose in company.Buffer(2, 1)let diff = (openClose[1] – openClose[0])/openClose[0]where diff > 0.1
0.034
0.104
-0.01
5
0.036
-0.06
9
Stock Trade Analysis
MSFT27.01
ticks
INTC21.75
MSFT27.96
MSFT31.21
INTC22.54
INTC20.98
MSFT30.73
from tick in ticksgroup tick by tick.Symbol into companyfrom openClose in company.Buffer(2, 1)let diff = (openClose[1] – openClose[0]) / openClose[0]where diff > 0.1
select new { Company = company.Key, Increase = diff }
res
Company = MSFTIncrease = 0.104
Stock Trade Analysis
Observable を変形・加工する合成可能な関数を利用する
Composable Functions
Transform: map, flatmap, reduce, Scan ...
Filter: take, skip, sample, takewhile, filter ...
Combine: concat, merge, zip, combinelatest, multicast, publish, cache, refcount ...
Concurrency: observeon, subscribeon Error Handling: onErrorreturn,
onErrorResume ...
map
def map[B](f: A⇒B): Observable[B]
filter
def filter(p: A⇒Boolean): Observable[A]
merge
def merge(Observable[A],Obserbal[A]): Observable[A]
def flatMap(f: T=>Observable[S]): Observable[S]= { map(f).flatten }
flatmap
val xs: Observable[Int] = Observable(3,2,1)val yss: Observable[Observable[Int]] = xs.map(x => Observable.Interval(x seconds) .map(_=>x).take(2))val zs: Observable[Int] = yss.flatten()
xs: Observable[Int]
yss: Observable[Observable[Int]]]
zs: Observable[Int]
flatmap
merge
concat
val xs: Observable[Int] = Observable(3,2,1)val yss: Observable[Observable[Int]] = xs.map(x => Observable.Interval(x seconds) .map(_=>x).take(2))val zs: Observable[Int] = yss.concat()
xs: Observable[Int]
yss: Observable[Observable[Int]]]
zs: Observable[Int]
zip
def groupBy[K](keySelector: T⇒K) : Observable[(K,Observable[T])]
groupBy
def startWith(ss: T*): Observable[T]
startWith
def reduce(f:(T, T) ⇒T): Observable[T]
reduce
Server サイドでの Rx の利用Netflix --- RxJava
“a library for composingasynchronous and event-basedprograms using observablesequences for the Java VM”
RxJavaJava 8 への Rx の移植
Observable.toObservable("one”,"two”,"three") .take(2) .subscribe( (arg) -> { System.out.println(arg); } );
https://github.com/Netflix/RxJava
Netflix
Netflix は、北米のインターネットのダウンロード・トラフィックの 33% を占める。
Netflix の顧客は、一月に 10億時間以上、テレビを見ている。
API のトラフィックは、 2010 年の 2000万 / 一日から、 2013 年には 20億 / 一日に成長した。
一日の Netflix API へのリクエスト数
クライアントのデバイスは、 Netflix の二つの主要なサービスに接続する。第一のサービスは、ビデオの検索やそのコンテンツの紹介に関係した機能を提供する Netflix API である。第二のサービスは、ビデオ・ストリームの再生サービスである。
ここでは、ビデオの検索・発見にかかわるNetflix API について見ていく。
Rx の発見は、システムの Re-Atchitecture から始まった
ネットワーク・トラフィックは、API の改良で、劇的に改善した
9 つあったネットワークの呼び出しは1つになり、 WAN でのネットワーク遅延は一回だけになった
クライアントのロジックは、サーバに移され、 20以上の呼び出しが削除された
関連情報
Netflix API architecturehttp://techblog.netflix.com/search/label/apihttps://speakerdeck.com/benjchristensen/
Re-architecturehttp://techblog.netflix.com/2013/01/optimizing-netflix-api.html
Hystrix https://github.com/Netflix/Hystrix
Netflix での Observable の利用
Netflix では、ブロックする API をObservable を返す非同期の API に作り替え、ついで、それらの Observable を返すAPI を組み合わせてサービスを構成した。
Observable.create({ observer ‐> try { observer.onNext(new ....)) observer.onCompleted(); } catch(Exception e) { observer.onError(e); }})
Observable<T> create(Func1<Observer<T>, Subscription> func)
Observable を作る
def Observable<VideoRating> getRating(userId, videoId) { return Observable.create({ observer ‐> executor.execute(new Runnable() { def void run() { try { VideoRating rating = //.. do network call . observer.onNext(rating) observer.onCompleted(); } catch(Exception e) { observer.onError(e); } } }) }) }
Asynchronous Observable with Single Values
def Observable<Video> getVideos() {return Observable.create({ observer ‐> executor.execute(new Runnable() { def void run() { try { for ( id in videoIds ) { Video v = // .. do network call ... observer.onNext(v) } observer.onCompleted(); } catch(Exception e) { observer.onError(e); } } }) }) }
Asynchronous Observable with Multiple Values
Observable を組み合わせて、サービスを作る
こうした情報を返すサービス
Observable<Video> は、 OnNext に n個のビデオを送り出す
Observable を組み合わせて、サービスを作る
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId)}
Observable<Video> は、 OnNext に n個のビデオを送り出す
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId) // リストの最初の10本だけが欲しい .take(10)}
最初の 10個を取って、あとは unscribe する。10個のビデオを送り出す Observable<Video> を返す。
最初の 10個を取って、あとは unscribe する。10個のビデオを送り出す Observable<Video> を返す。
Observable を組み合わせて、サービスを作る
最初の 10個を取って、あとは unscribe する。10個のビデオを送り出す Observable<Video> を返す。
unscribe
Observable を組み合わせて、サービスを作る
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId) // リストの最初の10本だけが欲しい .take(10) .map( { Video video -> // Video オブジェクトを変形する })}
map operator は、入力値を他の出力値に変える
Observable<R> b = Observable<T>.map({ T t -> R r = ... transform t ... return r;})
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId) // リストの最初の 10本だけが欲しい .take(10) .flatMap( { Video video -> // それぞれのビデオからメタデータを取り出す def m = video.getMetaData() .map( { Map<String, String> md -> return [ title: md.get(“title”), length: md.get(“duration”)] }) // ブックマークとレイティングも def b .... def r .... })}
ここでは、 flatMap / mapMany を使う。 map では Observable<T> の要素一つが、型 R の要素一つに変わったが、 flatMap / mapMany では、Observable<T> の要素一つ一つが、別の Observable<R> に変わる。
Observable を組み合わせて、サービスを作る
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId) // リストの最初の 10本だけが欲しい .take(10) .mapMany( { Video video -> // それぞれのビデオからメタデータを取り出す def m = video.getMetaData() .map( { Map<String, String> md -> return [ title: md.get(“title”), length: md.get(“duration”)] }) // ブックマークとレイティングも def b .... def r .... })}
Observable<VideoMetadata>Observable<VideoBookmark>Observable<VideoRating>
mapMany は、三つの関数の定義に応じてObservable<Video> から、次の三つのObservable を生成する。
一つの Video ‘v’ に対して getMetaData() は、Observable<MetaData> を返す
Observable を組み合わせて、サービスを作る
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId) // リストの最初の 10本だけが欲しい .take(10) .mapMany( { Video video -> // それぞれのビデオからメタデータを取り出す def m = video.getMetaData() .map( { Map<String, String> md -> return [ title: md.get(“title”), length: md.get(“duration”)] }) // ブックマークとレイティングも def b .... def r .... })}
それぞれの Observable は、 map を利用してデータを変換する
Observable<MetaData> は、 map 関数で変形される
Observable を組み合わせて、サービスを作る
Observable<Bookmark> 、 Observable<rating> も同様に処理される
Observable を組み合わせて、サービスを作る
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId) // リストの最初の 10本だけが欲しい .take(10) .mapMany( { Video video -> // それぞれのビデオからメタデータを取り出す def m = video.getMetaData() .map( { Map<String, String> md -> return [ title: md.get(“title”), length: md.get(“duration”)] }) // ブックマークとレイティングも def b .... def r .... // これらを結合する })}
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId) // リストの最初の 10本だけが欲しい .take(10) .flatmap( { Video video -> // それぞれのビデオからメタデータを取り出す def m ... def b .... def r .... // これらを結合する })}
def Observable<Map> getVideos(userId) { return VideoService.getVideos(userId) // リストの最初の 10本だけが欲しい .take(10) .flatmap( { Video video -> // それぞれのビデオからメタデータを取り出す def m ... def b .... def r .... // これらを結合する return Observable.zip( m, b, r, { metadata, bookmark, rating -> // 完全なデータの辞書に変換する return [ id: video.videoId ] << metadata << bookmark << rating }) })}
zip は、三つの非同期の Observable を一つの Observable にまとめる
Observable を組み合わせて、サービスを作る
Observable を組み合わせて、サービスを作る
public Data getData();を考える -- NetFlix RxJava
もしも、同期型から非同期型に変わったら、実装はどう変化するだろうか?クライアントは、ブロックしないように実行するには、どうすればいいだろうか?
getData の実装の候補 public Data getData(); public void getData(Callback<T> c); public Future<T> getData(); public Future<List<Future<T>>>
getData(); ...
?
Iterable と Observable
Iterable
Pull
T next()throw Exception
returns;
Observable
Push
onNext[T]onError[Exception]onCompletion
Iterable と Observable
同じ高階関数が、 Iterable にも Observableにも、同じように適用出来る。
getData() の分類 同期 / 非同期、返り値の単数 /複数で表を作ってみると、 getData() の次のような分類が可能である。
String s = getData(args);if (s.equals(x)) { // do something} else { // do something else}
scalar を返り値に持つ、典型的な同期型 getData() のコード
scalar 値が返る場合とほとんど同じだが、処理はループする
Iterable<String> values = getData(args);for (String s : values) { if (s.equals(x)) { // do something } else { // do something else }}
Future<String> s = getData(args);if (s.get().equals(x)) { // do something} else { // do something else}
古い Java の Future コード。非同期だが、 get でブロックする。
Future<String> s = getData(args);Futures.addCallback(s, new FutureCallback<String> { public void onSuccess(String s) { if (s.get().equals(x)) {...} else {...} } public void onFailure(Throwable t) { // handle error }}, executor);
Future と callback をつかった非同期のコード・サンプル
Observable を使った getData() のコード・サンプル
Observable<String> s = getData(args);s.map({ s -> if (s.get().equals(x)) { // do something } else { // do something else });
Iterable と Observable の「双対性」 Observable/Observer は、同期型の
Iterable/Iterator の非同期の双対形である。
“Subject/Observer is Dual to Iterator”Erik Meijer”http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf
“Introduction to the Reactive Framework Part II ”http://codebetter.com/matthewpodwysocki/2009/11/03/introduction-to-the-reactive-framework-part-ii/
Part IV
Reactive Manifesto
Reactive Manifesto
2013 年9月 23日 version 1.1http://www.reactivemanifesto.org/
Reactive に移行する必要性 近年、アプリケーションの要請は劇的に変わってき
た。ほんの数年前には、大規模なアプリケーションは、数十台のサーバ、数秒のレスポンスタイム、数時間のオフライン・メンテナンス、数ギガバイトのデータから構成されていた。今日、アプリケーションは、モバイル・デバイスから数千のマルチコア・プロセッサーが走るクラウド・ベースのクラスターにいたるまであらゆるところに配備されている。ユーザはミリセカンドさらにはナノセコンドのレスポンスタイムと、 100% の稼働を期待している。データの要求は、ペタバイトに拡大しようとしている。
当初は、 Google や Twitter といった革新的なインターネット・ドリブンの企業の領域だけだったが、こうしたアプリケーションの特徴は、ほとんどの産業で表面化した。金融や通信事業者が、最初に新しい要請を満たす実践を受け入れ、その他の産業がそれに続いた。
新しい要請は、新しいテクノロジーを要求する。それ以前のソリューションは、管理されたサーバとコンテナに力点を置いたものだった。規模拡大は、より大きなサーバを買うことと、マルチスレッドによる並行処理を通じて達成された。追加されたサーバは、複雑で非効率かつ高価なプロプライエトリなソリューションを通じて追加された。
しかし今では新しいアーキテクチャーが進化し、開発者が今日の要求を満たすアプリケーションを概念的に把握し、アプリを構築するのを可能にしている。我々は、これらをリアクティブ・アプリケーションと呼んでいる。このアプリケーションは開発者が、イベント・ドリブンで、拡張性を持ち、耐障害性の高い、応答性に優れたシステムを構築することを可能とする。すなわち、このアプリケーションは、リアルタイム感に富み、拡張可能で耐障害性の高いアプリケーション層で支えられ、マルチコアでもクラウド・コンピュータのアーキテクチャでもすぐに配備されうる、高い応答性のユーザ体験を提供出来るアプリケーションである。 この Reactive Manifesto は、リアクティブに移行するのに必要な本質的な諸特徴を記述するものである。
Reactive アプリケーション 辞書の Merriam-Webster は、 reactive を「刺激に対してすぐに反応できること」と定義している。すなわち、そのコンポーネントは「アクティブ」で、常にイベントを受け取る準備ができているということである。この定義は、リアクティブなアプリケーションの本質を捉えている。それはシステムの次のようなところに焦点を合わせている。
react to events イベントに反応するイベント・ドリブンの性質は、次のような特質を可能にする
react to load 負荷に反応する共有リソースの競合を避けることで、拡張可能性にフォーカスする
react to failure 失敗に反応する全てのレベルでエラー回復の能力を持たせることで、耐障害性の高いシステムを構築する
react to users ユーザに反応する負荷のいかんにかかわらず、高い反応時間を保証する
これらの一つ一つが、リアクティブなアプリケーションの本質的な特徴である。一方で、それらのあいだには、相互に従属関係が存在する。ただ、これらの特徴は、標準的な階層型のアプリケーションの意味での「層」にあたるものではない。その代わりに、それらは全ての技術をつらぬいた設計の諸特徴を記述したものである。
Event-driven重要なこと 非同期通信に基づいたアプリケーションは、疎結合
デザインを実装する。それは、純粋に同期型のメソッド呼び出しに基づいたアプリケーションよりもいいものである。イベントの送り手と受け手は、如何にイベントが伝播するのかの詳細を考えなくても実装されることが出来る。インターフェースは通信の内容に集中出来る。このことで、拡張も改良も保守も、より容易な実装ができるようになり、開発の柔軟性を増やすと同時に保守コストを低減出来る。
非同期通信のメッセージの受け手は、イベントが起きるまで、あるいは、メッセージを受け取るまで、眠っていることが出来るので、イベント・ドリブンのアプローチは、多数の受け取り手が、単一のハードウェア上のスレッドを共有することで、既存のリソースを有効に利用することが出来る。重い負荷の下にある、ブロックしないアプリケーションは、こうして、ブロックする同期型の通信単位に基づいた伝統的なアプリケーションより、低遅延と高スループットを持つことが出来る。結果として、オペレーションのコストは低いものになり、可用性は高まり、エンドユーザはハッピーになる。
Event-drivenキーとなる構成ブロック イベント・ドリブンのアプリケーションでは、様々
な事実を記述する離散型の情報の断片であるイベントの生産と消費を通じて、コンポーネントは相互作用を行う。これらのイベントは、ブロックしない非同期なスタイルで、送られ受け取られる。イベント・ドリブンのシステムは、 Pull や Poll より、 Push に依存することが多い。すなわち、イベントの消費者にデータをいつまでも要求するか待たせることなく、それが利用可能になった時点で消費者に向けてデータを Push する。
非同期のイベントの送信は、メッセージ・パッシングとも呼ばれるのだが、それはアプリケーションが設計から高度に並列化され、変更なしでマルチコアのハードウェアを利用出来るということを意味する。 CPU 内の全てのコアがいかなるメッセージ・イベントをも処理出来るということは、並行実行の可能性を劇的に増大させる。
ノン・ブロッキングは、エラーや処理の爆発的な増大が起きた場合でさえも、アプリケーションが、どんな時でも反応を返すことが出来る為に、処理を連続的に進める能力を意味する。この為には、反応に必要とされる全てのリソースは—例えば、 CPU 、メモリー、ネットワーク等は、独占されてはならない。こうして、それは低い遅延と高いスループットとより良い拡張性を可能とする。
伝統的なサーバサイドのアーキテクチャーは、変更可能な状態の共有と単一スレッド上のブロック操作に依存している。両者はともに、こうしたシステムを変化する要求に適合するようにスケールしようとした時遭遇する様々の困難の原因となる。変更可能な状態の共有は、同期を要求する。これは、思わぬ複雑さと非決定性を導入することになり、プログラムのコードの理解を難しいものにし、保守も難しいものにする。ブロッキングでスレッドを眠らせることは、有限の資源を浪費し、また、起き上がらせる時に高いコストを払うことになる。
イベントの生成と処理を分離することで、ランタイムのプラットフォームは、同期の詳細とスレッドをまたいでいかにイベントが送り出されるかを意識することが可能となる。一方で、プログラムの抽象はビジネスのワークフローのレベルまで持ち上げられる。スレッドやロックといった低レベルの諸機能にかかわることなしに、イベントがどのようにシステムの中を伝播し、コンポーネントがどのように相互作用するかを考えればいい。
イベント・ドリブンのシステムは、コンポーネント間、サブシステム間の疎結合を可能とする。この間接性のレベルは、あとで見るように、拡張可能性と耐障害性の前提条件の一つである。コンポーネント間の複雑で強い従属性を取り除くことで、イベント・ドリブンのアプリケーションは、既存のアプリケーションに最低限のインパクトを与えるだけで拡張が可能である。
アプリケーションが、高いパフォーマンスと大規模な拡張可能性を求める要求の圧力のもとにおかれた時、どこにボトルネックが生ずるかを予測するのは難しい。それ故、ソリューション全体が非同期でノン・ブロッキングなものであることが重要である。典型的な例では、このことは、 UI のユーザのリクエスト(ブラウザーなり、 REST のクライアントなりなんでも)からリクエストのパージングと web層での送り出しまで、ミドルウェアのサービス・コンポーネントに取っては、キャッシングからデータベースにいたるまで、デザインがイベント・ドリブンである必要があることを意味する。
もしこれらの層の一つでもこれに参加せず、データベースにブロックするような呼び出しを行ったり、変更可能な共有状態に依存したり、高価な同期処理を行ったとすれば全てのパイプラインはとどこおり、ユーザは遅延の増大と拡張可能性の減少に苦しめられるだろう。
アプリケーションは、その全体がリアクティブでなければならない。
この鎖の一番弱い環を取り除くことの必要性は、 Amadahl の法則に、よく表現されている。この法則は、 Wikipedia によれば次のように説明されている。
パラレル・コンピューティングで複数のプロセッサーを使ったプログラムのスピードアップは、そのプログラムのシーケンシャルな部分によって制限付けられる。例えば、プログラムの 95% が並列化されたとしても、パラレル・コンピューティングを使った理論上の最大のスピードアップは、次の図に示すように、どんなに多くのプロセッサーを使ったとしても、 20倍にとどまる。
Amdahl's Law
Scalable重要なこと “scalable” という言葉は、 Merriam-Webster によ
れば、次のように定義されている。 “要求に応じて、容易に拡張ないしは格上げが可能であること” .拡張可能なアプリケーションは、その利用に応じて拡張することが出来る。これは、アプリケーションに柔軟性を付加することで達成出来る。要求に応じてスケール・アウトまたはスケール・イン(ノードを追加または削除する)することが可能なオプションである。さらに、アーキテクチャは、アプリケーションの再デザインや書き直しなしで、スケール・アップまたはスケール・ダウン(ノードにより多くのあるいはより少ない CPU を配備する)することを容易にする。 柔軟性は、アプリケーションをクラウド環境で走らせるコストを最小化する。そして「使った分だけ払う」というモデルで、利益を得ることができる。
拡張可能性はまたリスクを管理するのを助ける。ユーザの負荷に対してあまりにも少ないハードウェアしか提供しないのは、ユーザの不満足を引き起こし、顧客を失うことになる。逆に、理由もなく遊んでいる多くのハードウェアとオペレーションの要員を与えるのは、不必要な経費でしかない。拡張可能なソリューションは、使うにふさわしい新しいハードウェアを使うことが出来ずに、アプリケーションを使い続けるリスクを緩和する。 10 年以内には、我々は、ことに数千ではないにしろ数百のハードウェア・スレッドが走るプロセッサーを見ることになるだろう。こうした潜在的な可能性は、アプリケーションが、非常に細かな粒度のレベルでスケーラブルであることを要求している。
Scalableキーとなる構成ブロック 非同期のメッセージ・パッシングに基づいたイベン
ト・ドリブンのシステムは、拡張可能性の基礎を提供する。コンポーネントとサブシステム間の疎結合と位置独立性は、同じセマンティックをもった同じプログラミング・モデルを維持したまま、システムを複数のノードにスケールアウトすることを可能にする。 コンポーネントのインスタンスを追加することで、システムのイベント処理の能力は増大する。実装に関して言えば、複数のコアを利用してスケール・アップすることと、データセンターやクラスターで沢山のノードを利用してスケール・アウトすることの間に違いはない。
アプリケーションのトポロジーは、アプリケーションの利用に応じた配置やランタイムのアルゴリズムの採用を通じて表現される配備の決定になる。このことを、位置透過性と呼ぶ。
目標は、透過的な分散コンピューティング、分散オブジェクト、 RPC スタイルの通信を実装しようと試みることなどではないのだと理解することは重要である。こうしたことは以前に試みられ、そして失敗してきた。我々に必要なことは、非同期メッセージ・パッシングを通じたプログラミング・モデルで直接に表現されるネットワークを擁護することなのである。真の拡張可能性は、分散コンピューティングを自然に含み、そしてそこには、本質的に信頼出来ないと言っていい、ネットワークをまたぐことを意味するノード間の通信が含まれている。
それゆえ、プログラミング・モデルの中で、物事を「簡単にする」という見落としの多い抽象の背後にそれらを隠すことなく、ネットワーク・プログラミングの制約やトレードオフや失敗のシナリオを明確にすることは重要である。結果として、分散環境で起きてくる典型的な問題を解決する為の共通の構成ブロックをカプセル化するツール – 高い段階の信頼性を提供する合意の形成やメッセージの抽象を与えるようなツールを提供することは、同じように重要なことである。
Resilient重要なこと アプリケーションのダウンは、ビジネスに起きうる
もっともダメージの多い問題の一つである。通常の帰結は、オペレーションが単に止まっただけでも、収入の流れに穴があいたままになるということである。長い期間でみれば、それは顧客の不満と悪い評判につながって、ビジネスをもっと深刻に傷つけることになりかねない。アプリケーションの対障害性が、大部分では無視されたり、経験的なテクニックで対応されている要請であることは驚くべきことである。このことは、あまりに荒い粒度のツールを使うという粒度のレベルの間違いに基づいていることを意味する。
実行時やサーバのエラーからの回復の為に、通常は、アプリケーション・サーバのクラスタリングの手法を用いる。不幸なことに、サーバのフェール・オーバーは、非常にコストがかかる、しかも危険なものである。それは、潜在的には、失敗の連鎖をおこして全てのクラスターの停止を招きかねない。コンポーネント・レベルの細粒度の耐障害性を利用して対応すべきことを、事故管理の間違ったレベルの粒度で対応しているというのは、こうした理由に基づく。
Merriam-Webster は、耐障害性を次のように定義している。 実体あるいはオブジェクトの、もとの形に戻ろう
とする能力。 困難からすぐに立ち戻る能力
リアクティブ・アプリケーションでは、耐障害性は結果論ではなく、最初からデザインの一部である。失敗を、プログラミング・モデルの中で、第一級のクラスの構成物とすることは、それに反応しそれを管理する手段を提供することを意味する。それは、アプリケーションを、それらを実行時に修復し回復する能力を持たせることで、失敗に対して高い耐久性を持たせることになる。伝統的な障害の処理は、こうしたことを達成出来ない。なぜなら、それは、例外が発生した時にはいつでもどこでもそれを処理するか、あるいは、障害が起きたらアプリケーションの全てのインスタンスをフェールオーバーするかということで、小規模なものではあまりに防御的で、大規模なものにはあまりに攻撃的だからである。
Resilientキーとなる構成ブロック 失敗を管理する為には、それが他の健全なコンポーネントに広がらないようにそれを隔離する手段と、また、失敗のコンテキストの外側の安全な地点からそれを観察する手段を必要とする。思い浮かぶ一つのパタンは、次の絵に示すような「隔壁」というパターンである。そこでは、システムはそれらに一つで事故が発生しても他の部分は影響を受けないように、安全な区域によって構成されている。これは、古典的な問題である事故の連鎖を防止し、管理の問題を隔離することを可能にする。
拡張可能性を可能とするイベント・ドリブン・モデルは、事故管理のこのモデルを実現する為の必要な基本的な単位でもある。イベント・ドリブン・モデルの疎結合は、事故はそのコンテキストとともに補足されるとともに、メッセージとしてカプセル化され、他のコンポーネントに送られてエラー検知を可能にしそれにどう対応するか決定出来るように、完全に隔離されたコンポーネントを提供する。
このアプローチは、ビジネス・ロジックはクリーンに残し、予期しなかったことの処理から分離したままで、事故は区分けされ観察され管理され、そして宣言的な仕方で処理されるようなシステム、そこでは、システムは自分自身を自動的に修復し回復出来るようなシステムを作り出す。それは、大きな企業では、問題はそれを処理する権限を持ったレベルの部署に到達するまで上層部に伝えられるのとほとんど同じように、区分けが階層的な仕方で構造化されていると最もうまく機能する。
このモデルの美しい点は、それが純粋にイベント・ドリブンで、リアクティブなコンポーネントと非同期のイベントに基づいており、それ故、位置通過的であることである。実践的には、このことは、分散環境においても、ローカルなコンテキストでの同じセマンティックでそれが動くことを意味する。
Responsive重要なこと Responsive は、 Merriam-Webster によれば「応答が素早く、適切に反応すること」と定義されている。我々は、この言葉をそうした一般的な意味で用いる。それは、 Web デザインにおける Responsive Web Design と混同されてはならない。それは、もともとは、 CSS の media query とその改良・強化にかかわるものだ。
Responsive アプリケーションは、リアルタイムで魅力的で豊かで共同的なものだ。ビジネスはその顧客と、レスポンシブでインタラクティブな経験を通じて顧客を歓迎することで、オープンで進行する対話を作り出すことが出来る。
それはビジネスをより効率的なものにし、人々はつながっていて問題を解決し課題を達成する手段を持っているという感覚を作り出す。一つの例が、 Google Doc である。 それはユーザがドキュメントを共同で編集することを可能とし、リアルタイムに、参加者が互いに編集やコメントを、書いたらすぐに生き生きと見ることが出来る。
イベントに反応するアプリケーションは、たとえ事故があったとしてもタイムリーな仕方でそれを行う必要がある。もし、アプリケーションが、別の言い方をすると遅延と見なされる、適切な時間の制約以内で反応しなかったら、それは実際には使い物にはならなくて、それ故、耐障害性を持つとは考えられなくなる。
厳しい実時間システムの制約に応えることが出来ないことは、軍事や医療コントロールシステムのようなアプリケーションの場合には、システム全体の事故と見なされる。全てのアプリケーションが、こうした厳しい要請を持つ訳ではない。多くのアプリケーションは、応答時間の制約から外れると、急速に有用性が失われるのを見ることになる。例えば、金融トレーディングのアプリケーションは、タイムリーな応答なしには、現在の取引を失うことになる。
小売りやネット購買のような、主流のより多くのアプリケーションは、反応時間が遅くなるにつれ確実に有用性が落ち始める。ユーザが、レスポンシブなアプリケーションと相互作用すればするほど、購買のボリュームは増大する。
Responsiveキーとなる構成ブロック リアクティブなアプリケーションは、 Observable
なモデルとイベント・ストリーム、クライアントの状態を利用する。
Observable モデルは、状態が変わった時に、他のシステムがイベントを受け取ることを可能とする。このことは、ユーザとシステムとのあいだにリアルタイムの接続を提供する。例えば、複数のユーザが同じモデルで同時に仕事をしていた時、変化は彼らの間でリアクティブに双方向に同期されうる。こうして、そのモデルはロックの制約なしに共有されているように見える。
イベント・ストリームは、その上にこうした接続が構築される基本的な抽象を形成する。これらをリアクティブにすることは、ブロッキングを排除して、その代わりに、非同期でノンブロッキングな転送と通信を可能とする。
リアクティブ・アプリケーションは、デザイン・パターンとテストを利用してアルゴリズムの順序を守る。これによって負荷のいかんにかかわらずイベントへの応答が O(1) あるいは少なくとも O(log n)時間で返ることを保証する。このスケール因子には、顧客、セッション、製品、取引を含むことが出来るが、それに限られない。
それらは、負荷のプロファイルのいかんにかかわらず応答の遅延を一定にするように、いくつかの戦略を利用している。
爆発的なトラフィックの条件下では、リアクティブ・アプリケーションは、遅延を一定のものに保つ為に、リソースをある了解と配慮の下で、結合したバッチ処理を適用して、 IO や同時並行でのデータの交換のような高価な処理を、後回しにする。
キューは適切なバックの圧力で制限されている。キューの長さは、与えられたレスポンスの制約条件の下で、 Little の法則を適用することで決定される。
システムは、その場で計画された適切な容量でモニターされる。
事故は、回路遮断機がトリガーされると、あらかじめ利用可能にされている代替処理戦略によって隔離される。
レスポンシブ・アプリケーションの例として、ブラウザ・ベースであれモバイル・アプリであれ、魅力的なユーザ経験を作り出す、リッチ・クライアントの Web アプリを考えよう。このアプリケーションは、クライアント側でロジックを実行し状態を記憶しているしよう。 Observable モデルは、データが変更された時、リアルタイムでユーザ・インターフェースを変更するメカニズムを提供する。 WebSocket や Server-Sent-Event のような技術が、ユーザ・インターフェースを直接にプッシュされたイベント・ストリームに接続することを可能とする。こうして、イベント・ドリブン・システムは、バックエンドからクライアントにいたるまで、あらゆる方法で拡張される。このことが、リアクティブ・アプリケーションが、非同期でノン・ブロッキングなデータ転送を使って、ブラウザやモバイル・アプリに、スケーラブルで耐障害性を持ったやり方で、イベントをプッシュすることを可能とする。
このことを心に留めておけば、いかにして四つの特質、イベント・ドリブン、拡張可能性、耐障害性、レスポンシブが、相互に関連し全体として密接に結びついていることは、明らかになる。
結論 リアクティブ・アプリケーションは、ソフトウェア
開発の現代における幅広い挑戦に向けられたバランスの取れたアプローチを表現している。イベント・ドリブン、メッセージ・ベースの基礎の上に硬直されることで、それは拡張可能性と耐障害性を保証するのに必要なツールを提供している。これらの上に、それは豊かでレスポンシブなユーザ・インタラクションをサポートしている。我々は、この数年以内にこの青写真に従うシステムの数が、急速に増えていくことを期待している。
参考資料
Meteor AngularJS polymer.js
Meteor Concept
Structuring your app Data and security Reactivity Live HTML Templates
Meteor アプリケーションの構造
Meteor アプリケーションは、クライアントの Webブラウザの内部で走る JavaScript と、 Node.js コンテナーの内部の Meteor サーバー上で走るJavaScript 、それにサポートされている全てのHTML フラグメントと CSS ルール、静的なアセットから構成される。
Meteor は、これらの異なるコンポーネントのパッケージングと連携を自動化する。ファイル・ツリーの中で、これらのコンポーネントに、どのような構造を選ぶかについては、 Meteor は、極めて柔軟である。
Data and security
In Memory DB Cache
全ての Meteor クライアントは、イン・メモリーのデータベース・キャッシュを持っている。
このクライアントのキャッシュを管理する為に、サーバーは、 JSON ドキュメントの集合を publishし、クライアントは、これらの集合に subscribeする。
集合内のドキュメントが変化すると、サーバーは、それぞれのクライアントのキャッシュを変更する。
publish function
それぞれのドキュメントの集合は、サーバー上のpublish 関数によって定義される。
publish 関数は、新しいクライアントが、ドキュメントの集合に対して subscribe するたびに、実行される。
ドキュメントの集合の中のデータは、どこから来てもいいのだが、一般的な場合は、データベースのクエリーを publish することである。
// server: publish all room documentsMeteor.publish("all-rooms", function () { return Rooms.find(); // everything);// server: publish all messages for a given roomMeteor.publish("messages", function (roomId) { return Messages.find({room: roomId});});
// server: publish the set of parties the logged-in user can see.Meteor.publish("parties", function () { return Parties.find({$or: [{"public": true}, {invited: this.userId}, {owner: this.userId}]});});
subscribe
いったん subscribe されると、クライアントは、そのキャッシュを高速なローカル・データベースとして利用するので、劇的に、クライアントのコードは、単純化される。
Read は、サーバーへの高価な回り道を、要求されることはない。 そして、それらは、そのキャッシュの内容に限られている。クライアント上の集合中のドキュメント全てに対するクエリーは、サーバーがクライアントに publish したドキュメントを返すのみである。
// client: start a parties subscriptionMeteor.subscribe("parties");
// client: return array of Parties this client can readreturn Parties.find().fetch(); // synchronous!
allow/deny rule
クライアントが、いくつかのドキュメントを変更した時、クライアントはサーバーに、その変更を要求するメッセージを送る。
サーバーは、提案された変更を、 JavaScript の関数で書かれた allow/deny ルールに照らしてチェックする。
サーバーは、全てのルールをパスした変更のみを受け入れる。
// server: don't allow client to insert a partyParties.allow({ insert: function (userId, party) { return false; }});
// client: this will failvar party = { ... };Parties.insert(party);
データの更新と伝搬
サーバーが変更を受け付けると、サーバーは、データベースにその変更を適用して、影響を受けたドキュメントに subscribe していた他のクライアントに、その変更を、自動的に伝搬する。
もし、受け付けなかったら、更新は失敗する。サーバーのデータベースは、変わらないまま残り、他のクライアントは、誰も、更新を見ることはない。
データの更新と伝搬
Meteor は、キュートなトリックも使う。クライアントがサーバーに対して書き込みをしようとした時、サーバーの返答を待つこと無しに、ローカルキャッシュをただちに書き換える。このことは、スクリーンは、ただちに再描画されることを意味する。
もし、サーバーが更新を受け付けた時、これは 、クライアントが正しく動作している時には、大部分の時間は、そうなるべきなのであるが、クライアントは、変化にただちにジャンプして、スクリーンを更新するのに、サーバーへの回り道を待つ必要がない。
Reactivity
Reactive Programming
Meteor は、 reactive programming のコンセプトを擁護する。このことは、コードを単純な命令スタイルで書くことを可能にし、その結果は、コードに従って、データが変更された時にはいつでも、自動的に再計算されることを意味する。
reactive context この自動的な再計算は、 Session と
Meteor.autosubscribe の協調によって達成される。
Meteor.autosubscribe のようなメソッドは、” reactive context” を確立する。そのコンテキストの中で、データの従属性が追跡され、必要な時に、関数の引数を再実行するように準備している。
Session のような Data provider は、それに対して、 それらが呼び出されたコンテキストと、どのようなデータが要求されているかを意識して、データが変更された時、 invalidation シグナルを送るように準備している。
reactive context + reactive data source
この reactive context + reactive data source という単純なパターンは、広い応用可能性をもっている。
それ以上に、プログラマーは、 unsubscribe/resubscribe の呼び出しのコードを書く手間が省け、それらは、正しい時に確実に呼び出されことになる
一般的に、 Meteor は、そうでなければ、誤りを犯しやすいロジックで、アプリケーションの邪魔になる、データ伝搬の全てのクラスを取り除くことが出来る、
reactive-context
次の Meteor 関数は、コードを、 reactive-contextで走らせる。 Templates Meteor.render and Meteor.renderList Meteor.autosubscribe Meteor.autorun
reactive data sources
変更をトリガーできる、 reactive data sources には、次のようなものがある。 Session variables Database queries on Collections Meteor.status Meteor.user Meteor.userId Meteor.userLoaded
LiveHTML
LiveHTML HTML templating は、 Web アプリの中心である。
Meteor のライブ・ページ更新技術で、 HTMLを、 reactive にレンダー出来る。それは、ページを生成するのに利用されたデータの変化を追跡して、自動的に更新が行われることを意味する。
この特徴は、全ての HTML テンプレート・ライブラリーで機能し、手で書かれた JavaScript で生成された HTML でも機能する。
var fragment = Meteor.render( function () { var name = Session.get("name") || "Anonymous"; return "<div>Hello, " + name + "</div>"; });document.body.appendChild(fragment);
Session.set(“name”, “Bob”); // ページは自動的に更新される
LiveHTML の例
Meteor.render()
Meteor.render は、 rendering function 、あるHTML を文字列として返すを関数を引数に取る。それは、自動更新する DocumentFragment を返す。
rendering function で利用されたデータに変更があったとき、それは、再実行される。 DocumentFragment 内の DOMノードは、ページのどの場所に挿入されていても、その場で自分自身を更新する。それは全く自動的である。
Meteor.render は、 rendering function によって、どのデータが利用されたかを発見する為にreactive context を使う。
Template
<head> <title>Advanced Template Demo</title></head><body> {{> page}}</body>
<template name="page"> <h1>Advanced Template Demo</h1> <p> This demo shows off the advanced features of Meteor's optional </p>
{{> preserveDemo }} {{> constantDemo }} {{> stateDemo }} {{> d3Demo }}</template>
<template name="preserveDemo"> <h2>Element preservation</h2>
<input type="button" value="X++" class="x”> ...</template>
<template name="constantDemo"> <h2>Constant regions</h2>
<div> <input type="button" value="X++" class="x"> <br> <input type="checkbox" class="remove" which="1" {{checked 1}}> Remove map 1<br> <input type="checkbox" class="remove" which="2" {{checked 2}}> Remove map 2 </div> ...
<template name="hello"> <div class="greeting">Hello there, {{first}} {{last}}!</div></template>
// in the JavaScript console> Template.hello({first: "Alyssa", last: "Hacker"}); => "<div class="greeting">Hello there, Alyssa Hacker!</div>"
Meteor.render(function () { return Template.hello({first: "Alyssa", last: "Hacker"});}) => automatically updating DOM elements
データベースへのクエリー
<template name="players"> {{#each topScorers}} <div>{{name}}</div> {{/each}}</template>
Template.players.topScorers = function () { return Users.find({score: {$gt: 100}}, {sort: {score: -1}});};
// in a JavaScript fileTemplate.players.leagueIs = function (league) { return this.league === league;};
<template name="players"> {{#each topScorers}} {{#if leagueIs "junior"}} <div>Junior: {{name}}</div> {{/if}} {{#if leagueIs "senior"}} <div>Senior: {{name}}</div> {{/if}} {{/each}}</template>
// Works fine with {{#each sections}}Template.report.sections = ["Situation", "Complication", "Resolution"];
<template name="scores"> {{#each player}} {{> playerScore}} {{/each}}</template><template name="playerScore"> <div>{{name}}: {{score}} <span class="givePoints">Give points</span> </div></template>
Template.playerScore.events({ 'click .givePoints': function () { Users.update({_id: this._id}, {$inc: {score: 2}}); }});
Meteor Examples
<head> <title>Leaderboard</title></head>
<body> <div id="outer"> {{> leaderboard}} </div></body>
<template name="leaderboard"> <div class="leaderboard"> {{#each players}} {{> player}} {{/each}} </div>
{{#if selected_name}} <div class="details"> <div class="name">{{selected_name}}</div> <input type="button" class="inc" value="Give 5 points" /> </div> {{/if}}
{{#unless selected_name}} <div class="none">Click a player to select</div> {{/unless}}</template>
<template name="player"> <div class="player {{selected}}"> <span class="name">{{name}}</span> <span class="score">{{score}}</span> </div></template>
Players = new Meteor.Collection("players"); .......if (Meteor.isServer) { Meteor.startup(function () { if (Players.find().count() === 0) { var names = ["Ada Lovelace", "Grace Hopper", "Marie Curie", "Carl Friedrich Gauss", "Nikola Tesla", "Claude Shannon"]; for (var i = 0; i < names.length; i++) Players.insert({name: names[i], score: Math.floor(Math.random()*10)*5}); } });}
Players = new Meteor.Collection("players");
if (Meteor.isClient) { Template.leaderboard.players = function () { return Players.find({}, {sort: {score: -1, name: 1}}); };
Template.leaderboard.selected_name = function () { var player = Players.findOne(Session.get("selected_player")); return player && player.name; };
Template.player.selected = function () { return Session.equals("selected_player", this._id) ? "selected" : ''; }
Template.leaderboard.events({ 'click input.inc': function () { Players.update( Session.get("selected_player"), {$inc: {score: 5}}); } });
Template.player.events({ 'click': function () { Session.set("selected_player", this._id); } });}
Template.leaderboard.events({ 'click input.inc': function () { Players.update( Session.get("selected_player"), {$inc: {score: 5}}); } });
Template.player.events({ 'click': function () { Session.set("selected_player", this._id); } });}
Template.leaderboard.events({ 'click input.inc': function () { Players.update( Session.get("selected_player"), {$inc: {score: 5}}); } });
Template.player.events({ 'click': function () { Session.set("selected_player", this._id); } });}
AngularJS Developer GuideConceptual Overview
http://docs.angularjs.org/guide/concepts
Hello World デモ
<!doctype html><html ng-app> <head> <script src="http://code.angularjs.org/1.2.0rc1/angular.min.js"> </script> </head> <body> <p ng-init=" name='World' ">Hello {{name}}!</p> </body></html>
Hello World Startup1. ブラウザーは、 HTML をロードして DOM にパーズする。2. ブラウザーは、 angular.js スクリプトをロードする。3. Angular は、 DOMContentLoaded イベントを待つ。4. Angular は、 ng-app directive を探す。それがアプリケー
ションの境界を指定する。5. ng-app で指定されたモジュールが、 $injector を構成する。6. $injector は、 $compile サービスと $rootScope を生成する
為に利用される。7. $compile サービスは、 $rootScope の元で、 DOM とリンク
をコンパイルする為に利用される。8. ng-init directive が、スコープの名前属性に「World」を割り当てる。
9. {{name}} が、表現式を「 Hello World!」に補完する。
Data Binding デモ
<!doctype html><html ng-app> <head> <script src="http://code.angularjs.org/1.2.0rc1/angular.min.js"> </script> </head> <body> <input ng-model="name"> <p>Hello {{name}}!</p> </body></html>
コンパイル時
1. ng-model と input directiveは、 <input> コントロール上に、 keydown listener を設定する。
2. {{name}} 補完は、名前が変わった場合に、 $watch に通知がいくように設定される。
実行時1. ‘X’ key が押されると、ブラウザーは input コントロールに
keydown イベントを発する。2. input directive は、 input の値の変化を捉えて、 Angular 実
行コンテキスト内のアプリケーション・モデルを変更する為に、 $apply(“name = ‘X’;”) を呼び出す。
3. Angular は、 name = ‘X’; をモデルに適用する。 4. $digest ループが始まる。5. $watch リストは、 name 属性の変化を検出すると、
{{name}} 補完に通知をして、それが、つづいて DOM を更新する。
6. Angular は実行コンテキストを抜け出す。それで、 keydownイベントは終わり、それとともに JavaScript の実行コンテキストも終わる。
7. ブラウザーは、更新されたテキストで、ビューを再描画する。
Controller デモ
Controller デモ<!doctype html><html ng-app> <head> <script src="http://code.angularjs.org/1.2.0rc1/angular.min.js"> </script> <script src="script.js"></script> </head> <body> <div ng-controller="GreetCtrl"> Hello {{name}}! </div> <div ng-controller="ListCtrl"> <ol> <li ng-repeat="name in names">{{name}}</li> </ol> </div> </body></html>
Controller デモ
function GreetCtrl($scope) { $scope.name = 'World';} function ListCtrl($scope) { $scope.names = ['Igor', 'Misko', 'Vojta'];}
.show-scope .doc-example-live.ng-scope,
.show-scope .doc-example-live .ng-scope { border: 1px solid red; margin: 3px;}
Controller
controller は、 view の背後にあるコードである。その役割は、モデルを構築して、コールバック・メソッドにそって、それをビューに公開することである。
view は、 scope の template( HTML)上への投射である。
scope は、モデルを view につなげ、イベントを Controller に向けさせる「つなぎ」の役割を果たす。
The separation of the controller and the view controller は、 JavaScript で書かれる。
JavaScript は命令型である。命令型は、アプリケーションの振る舞いを指定するには、もっとも適している。 controller は、( DOM への参照、 HTML の断片のような)レンダー用の情報を含むべきではない。
view template は HTML で書かれる。 HTML は宣言型である。宣言型は、 UI の指定に最適である。 view は、振る舞いを含むべきではない。
controller は view のことを知らないので、同一のcontoroller に対して複数の view があり得る。このことは、スキンの変更・デバイス特有の view(モバイルと PC)・テスト可能性に取って、重要である。
Model model は、 template とマージされて view を生み
出すデータである。 model が view にレンダーされる為には、 model は、 scope から参照されなければならない。
他の多くのフレームワークとは異なって、 Angularは、 model について何の制限も要請も設けてはいない。 model にアクセス、あるいは model を変更する為に、継承すべきクラスも、特別のアクセス・メソッドもない。
model は、プリミティブでも、オブジェクト・ハッシュでも、全くのオブジェクト型でも構わない。要するに、 model は、” plain JavaScript object” なのである。
View
view は、ユーザーが見るものである。 viewのライフサイクルは、 template として始まる。 view は、 model とマージされて、最終的には、ブラウザーの DOM にレンダーされる。
Angular は、他の大部分の template システムと比較して、全く異なるアプローチを採用している。
Angular の template システムは、文字列の上ではなく DOM オブジェクトの上で動く。template は確かに HTML で書かれているが、それは HTML である。
View
ブラウザーは、 HTML をパーズして DOM にし、 DOM は、コンパイラーとして知られている templateエンジンの入力になる。
コンパイラーは、 directive を探して、それが Model上に watches を設定する。その結果は、 template model の再マージを必要としない連続的な view の更新になる。
Model は、 view にとって、ただ一つの真実のソースになる。
Module と Injector
Injector は、 Service Locator である。 Angular アプリケーションごとに、一つの
injector が存在する。 injector は、オブジェクトのインスタンスを名前で探す方法を提供する。
injector は、全てのオブジェクトを内部のキャッシュに保持し、同じ名前のオブジェクトを取得する為の呼び出しの繰り返しが、同じインスタンスを返すようにする。もし、オブジェクトが存在しなければ、その時はinjector は、インスタンス factory に、新しいインスタンスの生成を依頼する。
// Create a modulevar myModule = angular.module('myModule', []) // Configure the injectormyModule.factory('serviceA', function() { return { // instead of {}, put your object creation here };}); // create an injector and configure it from 'myModule'var $injector = angular.injector(['myModule']); // retrieve an object from the injector by namevar serviceA = $injector.get('serviceA'); // always true because of instance cache$injector.get('serviceA') === $injector.get('serviceA');
// You write functions such as this one.function doSomething(serviceA, serviceB) { // do something here.} // Angular provides the injector for your applicationvar $injector = ...; ///////////////////////////////////////////////// the old-school way of getting dependencies.var serviceA = $injector.get('serviceA');var serviceB = $injector.get('serviceB'); // now call the functiondoSomething(serviceA, serviceB); ///////////////////////////////////////////////// the cool way of getting dependencies.// the $injector will supply the arguments to the function automatically$injector.invoke(doSomething); // This is how the framework calls your functions
Time Example
<!doctype html><html ng-app="timeExampleModule"> <head> <script src="http://code.angularjs.org/1.2.0rc1/angular.min.js"> </script> <script src="script.js"></script> </head> <body> <div ng-controller="ClockCtrl"> Current time is: {{ time.now }} </div> </body></html>
Time Exampleangular.module('timeExampleModule', []). // Declare new object called time, // which will be available for injection factory('time', function($timeout) { var time = {}; (function tick() { time.now = new Date().toString(); $timeout(tick, 1000); })(); return time; }); // Notice that you can simply ask for time// and it will be provided. No need to look for it.function ClockCtrl($scope, time) { $scope.time = time;}
polymer.jsModel Driven Views
http://www.polymer-project.org/platform/mdv.html
<head> <script src="mdv.js"></script></head><body> <h1>Model-driven Views</h1> <ul> <template id="greeting" repeat="{{ salutations }}"> <li>{{ what }}: <input type="text" value="{{ who }}"></li> </template> </ul><script>var t = document.getElementById('greeting');var model = { salutations: [ { what: 'Hello', who: 'World' }, { what: 'GoodBye', who: 'DOM APIs' }, { what: 'Hello', who: 'Declarative' }, { what: 'GoodBye', who: 'Imperative' } ]};t.model = model;</script></body>
Template Instantiation MDV
<template bind="{{ singleton }}"> Creates a single instance with {{ bindings }} when singleton model data is provided.</template>
<template repeat="{{ collection }}"> Will create maintain exactly instance with {{ bindings }} for every element in the array collection, when it is provided.</template>
bind
repeat
Template Instantiation MDV
<template bind if="{{ conditionalValue }}"> Binds if and only if conditionalValue is truthy.</template>
<template if="{{ conditionalValue }}"> Binds if and only if conditionalValue is truthy. (same as *bind if*)</template>
<template repeat if="{{ conditionalValue }}"> Repeat if and only if conditionalValue is truthy.</template>
if
Template Instantiation MDV
<template id="myTemplate"> Used by any template which refers to this one by the ref attribute</template>
<template bind ref="myTemplate"> When creating an instance, the content of this template will be ignored, and the content of #myTemplate is used instead.</template>
ref
// Causes any bind, repeat or if attribute directives on // #myTemplate to begin acting.document.getElementById('myTemplate').model = jsData;
Node.bind() MDV
var obj = { path: { to: { value: 'hi' } }};
var textNode = document.createTextNode('test');textNode.bind('textContent', obj, 'path.to.value');
Node.bind() MDVBinding types
Text nodestextNode.bind('textContent', someObj, 'path.to.value');
Element attribute valuesmyElement.bind('title', someObj, 'path.to.value');
Element attribute presencemyElement.bind('hidden?', someObj, 'path.to.value');
Input element value and checked propertiesmyValueInput.bind('value', someObj, 'path.to.value');myCheckboxOrRadioInput.bind('checked', someObj, 'path.to.value');