リアクティブプログラミングの抂芁

こんにちは。 この蚘事では、ペヌロッパを駆け巡りたす。぀たり、リアクティブプログラミングの意味を説明し、アクタヌ、リアクティブフロヌを玹介し、最埌にリアクティブフロヌを䜿甚しお、叀いOperaずその粟神的な埌継者であるVivaldiのように、マりスゞェスチャヌを認識したす。



目暙は、リアクティブプログラミングの基本抂念を玹介し、すべおが䞀芋するず思えるほど耇雑で怖くないこずを瀺すこずです。



画像

出所



リアクティブプログラミングずは䜕ですか



この質問に答えるために、 サむトに目を向けたす 。 リアクティブアプリケヌションが満たさなければならない4぀の䞻芁な基準を瀺す矎しい画像がありたす。



画像



アプリケヌションは高速で、フォヌルトトレラントであり、拡匵性に優れおいる必芁がありたす。

「私たちはすべおの善ずすべおの悪のために」のように芋えたすよね



これらの蚀葉が意味するもの



  1. 応答性



    アプリケヌションは、ナヌザヌに0.5秒で結果を提䟛する必芁がありたす。 フェむルファヌストの原理もこれに起因する可胜性がありたす。぀たり、䜕か問題が発生した堎合、「申し蚳ありたせんが、問題が発生したした。 倩気を海蟺で埅぀よりも埌で再詊行しおください。 操䜜が長い堎合、進行状況バヌをナヌザヌに衚瀺したす。 非垞に長い堎合-「あなたのリク゚ストは、2042幎3月18日に実行されたす。 通知を郵送したす。」
  2. スケヌラビリティは、負荷がかかったずきに応答性を提䟛する方法です。 比范的成功したサヌビスのラむフサむクルを想像しおください。

    1. 起動-芁求フロヌは小さく、サヌビスは1぀のコアを持぀仮想マシンで実行されたす。
    2. リク゚ストのフロヌが増加したす-カヌネルが仮想マシンに远加され、リク゚ストは耇数のスレッドで凊理されたす。
    3. さらに負荷がかかりたす-バッチ凊理に接続したす-デヌタベヌスずハヌドドラむブぞの芁求はグルヌプ化されたす。
    4. さらに倚くの負荷-より倚くのサヌバヌを䞊げお、クラスタヌで䜜業を提䟛する必芁がありたす。

      理想的には、システム自䜓が負荷に応じお拡倧たたは瞮小する必芁がありたす。
  3. 耐障害性



    私たちは䞍完党な䞖界に䜏んでおり、すべおが起こるこずを受け入れたす。 システムで䜕か問題が発生した堎合、゚ラヌ凊理ず回埩方法を提䟛する必芁がありたす
  4. そしお最埌に、アヌキテクチャがメッセヌゞ駆動型メッセヌゞングに基づいおいるシステムを䜿甚しお、これらすべおを達成するように招埅されおいたす。


続行する前に、むベント駆動型システムずメッセヌゞ駆動型システムの違いに぀いお詳しく説明したす。



むベント駆動型





むベント駆動型ずは察照的に、メッセヌゞ駆動型システムでは





これはすべお私たちに提䟛しおいたす



俳優モデル



開発のマむルストヌン





俳優は䜕ができたすか



アクタヌは同じオブゞェクトですが、次のずおりです。





䟋を考えおみたしょう。



画像



アクタヌAは、アクタヌBにメッセヌゞを送信したいず考えおいたす。圌が持っおいるのは、ActorRefアドレスだけです。 アクタヌBはどこでもかたいたせん。

アクタヌAは、システムActorSystemを介しお文字Bを送信したす。 システムは、アクタヌBのメヌルボックスにレタヌを入れ、アクタヌBを「りェむクアップ」したす。アクタヌBは、メヌルボックスからレタヌを受け取り、䜕かを実行したす。



別のオブゞェクトでメ゜ッドを呌び出すこずに比べお、それは䞍必芁に耇雑に芋えたすが、アクタヌが特定の刺激に反応しお䜕かをするように蚓緎された人だず想像するず、アクタヌのモデルは珟実の䞖界に完党に適合したす。



父ず息子を想像しおください







父芪は息子のSMSkuに「郚屋の掃陀」を送り、自分のこずを続けたす。 息子はSMSkuを読み、クリヌニングを開始したす。 䞀方、父はポヌカヌをしおいたす。 息子は掃陀を終了し、SMS「完了」を送信したす。 簡単そうですね。



ここで、父ず息子は俳優ではなく、互いのメ゜ッドを匕っ匵る普通のオブゞェクトであるず想像しおください。 父芪は「郚屋を掃陀する」方法のために息子を匕っ匵り、息子が掃陀を終えお父芪にコントロヌルを戻すたで埅぀かかずを远いたす。 珟時点では、父芪はポヌカヌをプレむできたせん。 このコンテキストでは、アクタヌモデルがより魅力的になり぀぀ありたす。



では、に移りたしょう



Akka.NET



以䞋に曞かれおいるこずはすべお、JVMの元のAkkaに圓おはたりたすが、私にずっおは、CはJavaよりも近いため、Akka.NETを䟋ずしお䜿甚したす。



Akkaの利点は䜕ですか





しかし、スケヌリングのトピックは非垞に広範囲であり、別の出版物に倀したす。 したがっお、すべおのプロゞェクトで圹立぀機胜に぀いおのみ詳しく説明したす。



゚ラヌ凊理



アクタヌには階局があり、ツリヌずしお衚すこずができたす。 各俳優は芪を持ち、「子䟛」を持぀こずができたす。



画像

Akka.NETドキュメント Copyright 2013-2018 Akka.NETプロゞェクト



各アクタヌに察しお、監督戊略を蚭定できたす。「子䟛」に䜕か問題が発生した堎合の察凊方法です。 たずえば、問題のある俳優を「打ち負かし」、同じタむプの新しい俳優を䜜成し、同じ仕事を圌に任せたす。



たずえば、Akka.net CRUDでアプリケヌションを䜜成したした。このアプリケヌションでは、「ビゞネスロゞック」の局がアクタヌに実装されおいたす。 このプロゞェクトの目的は、スケヌラブルでないシステムでアクタヌを䜿甚する必芁があるかどうか、぀たり、圹者が生掻を改善するのか、痛みを増すのかを調べるこずでした。



Akkaの組み蟌み゚ラヌ凊理がどのように圹立぀か



GIF




  1. すべおが正垞で、アプリケヌションが機胜し、
  2. リポゞトリに䜕かが起こりたしたが、5回のうち1回だけ結果が出たす
  3. 私は、監督戊略を「毎秒10回詊行」に蚭定したした。
  4. アプリケヌションはゆっくりではあるが再び動䜜し、問題が䜕であるかを理解する時間がありたす。


「さあ、このような゚ラヌ凊理を自分で曞いおみよう。なぜ誰かがミスをするのか」 公正な発蚀ですが、障害点が少ない堎合のみです。



そしお、少しのコヌド。 これが、IoCコンテナヌ内のアクタヌシステムの初期化の様子です。



public Container() { system = ActorSystem.Create("MySystem"); var echo = system.ActorOf<EchoActor>("Echo"); //stop initialization if something is wrong with actor system var alive = echo.Ask<bool>(true, TimeSpan.FromMilliseconds(100)).Result; container = new WindsorContainer(); //search for dependencies //register controllers //register ActorSystem propsResolver = new WindsorDependencyResolver(container, (ActorSystem)system); system.AddDependencyResolver(propsResolver); actorSystemWrapper = new ActorSystemWrapper(system, propsResolver); container.Register(Component.For<IActorRefFactory>().Instance(actorSystemWrapper)); container.Register(Component.For<IDependencyResolver>().Instance(propsResolver)); }
      
      





EchoActorは、送信者に倀を返す最も単玔なアクタヌです。



  public class EchoActor : ReceiveActor { public EchoActor() { Receive<bool>(flag => { Sender.Tell(flag); }); } }
      
      





アクタヌを「通垞の」コヌドに接続するには、Askコマンドを䜿甚したす。



  public async Task<ActionResult> Index() { ViewBag.Type = typeof(Model); var res = await CrudActorRef.Ask<IEnumerable<Model>>(DataMessage.GetAll<Model>(), maxDelay); return View(res); }
      
      





合蚈



俳優ずスニッキヌ、私は蚀うこずができたす





パヌト2ゞェットストリヌム



次に、より䞀般的で䟿利なトピックであるゞェットフロヌに移りたしょう。 プロセスでアクタヌず䌚うこずができない堎合、フロント゚ンドずバック゚ンドの䞡方でRxストリヌムが確実に圹立ちたす。 それらの実装は、ほずんどすべおの最新のプログラミング蚀語で行われおいたす。 今日ではバック゚ンドのプログラマでさえJavaScriptで䜕かをしなければならないこずがあるため、RxJの䟋を挙げたす。





Rxストリヌムは、すべおの䞀般的なプログラミング蚀語で䜿甚できたす。



CC BY-NC 4.0でラむセンスされおいるAndre Staltzの 「 䞍足しおいるリアクティブプログラミングの抂芁 」



ゞェットストリヌムずは䜕かを説明するために、プルコレクションずプッシュコレクションから始めたす。

単䞀の戻り倀 耇数の戻り倀
匕く

同期

むンタラクティブ
T IEnumerable <T>
プッシュ

非同期

リアクティブ
タスク<T> IObservable <T>


プルコレクションは、プログラミングで私たちが慣れおいるものです。 最も顕著な䟋は配列です。



 const arr = [1,2,3,4,5];
      
      





すでにデヌタがあり、圌自身はこのデヌタを倉曎したせんが、リク゚ストに応じお提䟛できたす。



 arr.forEach(console.log);
      
      





たた、デヌタを凊理する前に、䜕らかの方法でデヌタを凊理できたす。



 arr.map(i => i+1).map(I => “my number is ”+i).forEach(console.log);
      
      





ここで、最初はコレクションにデヌタがないこずを想像しおみたしょうが、デヌタが衚瀺されたこずは間違いなく通知されたすプッシュ。 同時に、必芁な倉換をこのコレクションに適甚できたす。



䟋



 source.map(i => i+1).map(I => “my number is ”+i).forEach(console.log);
      
      





゜ヌスに1などの倀が衚瀺されるず、console.logは「my number is 1」を出力したす。



仕組み



新しい゚ンティティが衚瀺されたす-件名たたは芳枬可胜



 const observable = Rx.Observable.create(function (observer) { observer.next(1); observer.next(2); observer.next(3); setTimeout(() => { observer.next(4); observer.complete(); }, 1000); });
      
      





これは、状態の倉化に関する通知を送信するプッシュコレクションです。



この堎合、4秒埌に1、2、3の数字がすぐに衚瀺され、コレクションは「完了」したす。 これはこのような特別なタむプのむベントです。



2番目の゚ンティティはObserverです。 圌はサブゞェクトむベントにサブスクラむブし、受信したデヌタで䜕かをするこずができたす。 䟋



 observable.subscribe(x => console.log(x)); observable.subscribe({ next: x => console.log('got value ' + x), error: err => console.error('something wrong occurred: ' + err), complete: () => console.log('done'), }); observable .map(x => 'This is ' + x) .subscribe(x => console.log(x));
      
      





1぀のサブゞェクトが倚くのサブスクラむバヌを持぀こずができるこずがわかりたす。



簡単に芋えたすが、なぜこれが必芁なのかはただ明確ではありたせん。 リアクティブフロヌを䜿甚する際に知っおおく必芁のある定矩をさらに2぀瀺し、その埌、実際にそれらがどのように機胜し、どのような状況でその朜圚胜力が最倧限に発揮されるかを瀺したす。



冷たい芳枬可胜量





これはどういう意味ですか䌚瀟被隓者がギフトの配垃を手配するこずにしたずしたしょう。 各埓業員オブザヌバヌは仕事に来お、ギフトのコピヌを受け取りたす。 誰も取り残されおいたせん。



ホットオブザヌバブル





䟋朝、埓業員向けのホットケヌキが䌚瀟に持ち蟌たれたす。 圌らが連れお来られるず、すべおのひばりは匂いで飛んで、朝食のためにパむを䜜りたす。 しかし、埌に来たフクロりはもはやパむを取埗したせん。



どのような状況でゞェットストリヌムを䜿甚したすか



時間の経過ずずもに配信されるデヌタストリヌムがある堎合。 たずえば、ナヌザヌ入力。 たたは、任意のサヌビスからのログ。 プロゞェクトの1぀で、N秒でむベントを収集し、パック党䜓を同時に蚘録する自己蚘述型のロガヌを芋たした。 バッテリヌコヌドがペヌゞを占有したした。 Rxストリヌムが䜿甚された堎合、はるかに簡単になりたす。



画像

RxJs Reference / Observable 、CC BY 4.0の䞋でラむセンスされたドキュメント 。

リアクティブフロヌを䜿甚したさたざたな操䜜の実行内容を説明する倚くの䟋ず写真がありたす



 source.bufferTime(2000).subsribe(doThings);
      
      





そしお最埌に、䜿甚䟋。



Rxストリヌムでのマりスゞェスチャヌの認識



叀いOperaたたはその粟神的な埌継者であるVivaldiには、マりスゞェスチャヌを䜿甚したブラりザコントロヌルがありたした。



Gif-Vivaldiでのマりスゞェスチャヌ




぀たり、マりスの䞊䞋、巊右の動き、およびそれらの組み合わせを認識する必芁がありたす。 これはRxストリヌムなしで蚘述できたすが、コヌドは耇雑で維持が困難です。



そしお、Rxストリヌムでの衚瀺は次のずおりです。



最埌から始めたす-元のシヌケンスで怜玢するデヌタず圢匏を蚭定したす。



 //gestures to look for const gestures = Rx.Observable.from([ { name: "Left", sequence: Rx.Observable.from([{ x: -1, y: 0 }]) }, { name: "Right", sequence: Rx.Observable.from([{ x: 1, y: 0 }]) }, { name: "Up", sequence: Rx.Observable.from([{ x: 0, y: -1 }]) }, { name: "Down", sequence: Rx.Observable.from([{ x: 0, y: 1 }]) }, { name: "Down+Up", sequence: Rx.Observable.from([{ x: 0, y: 1 }, { x: 0, y: -1 }]) }, { name: "Up+Right", sequence: Rx.Observable.from([{ x: 0, y: -1 }, { x: 1, y: 0 }]) } ]);
      
      





これらは単䜍ベクトルずその組み合わせです。



次に、マりスむベントをRxストリヌムに倉換する必芁がありたす。 すべおのRxラむブラリには、暙準むベントをObservableに倉換するための組み蟌みツヌルがありたす。



 const mouseMoves = Rx.Observable.fromEvent(canvas, 'mousemove'), mouseDowns = Rx.Observable.fromEvent(canvas, 'mousedown'), mouseUps = Rx.Observable.fromEvent(canvas, 'mouseup');
      
      





次に、マりスの座暙を2でグルヌプ化し、それらの差を芋぀けお、マりスオフセットを取埗したす。



 const mouseDiffs = mouseMoves .map(getOffset) .pairwise() .map(pair => { return { x: pair[1].x-pair[0].x, y: pair[1].y-pair[0].y } });
      
      





むベント「mousedown」ず「mouseup」を䜿甚しおこれらの動きをグルヌプ化したす。



 const mouseGestures = mouseDiffs .bufferToggle(mouseDowns, x => mouseUps) .map(concat);
      
      





concat関数は、短すぎる動きを切り取り、方向がおおよそ揃っおいる動きをグルヌプ化したす。



 function concat(values) {//summarize move in same direction return values.reduce((a, v) => { if (!a.length) { a.push(v); } else { const last = a[a.length - 1]; const lastAngle = Math.atan2(last.x, last.y); const angle = Math.atan2(vx, vy); const angleDiff = normalizeAngle(angle - lastAngle); const dist = Math.hypot(vx, vy); if (dist < 1) return a;//move is too short – ignore //moving in same direction => adding vectors if (Math.abs(angleDiff) <= maxAngleDiff) { last.x += vx; last.y += vy; } else { a.push(v); } } return a; }, []); }
      
      





X軞たたはY軞に沿った動きが短すぎる堎合、れロにリセットされたす。 そしお、埗られた倉䜍座暙から笊号のみが残りたす。 したがっお、探しおいた単䜍ベクトルが取埗されたす。



 const normalizedMouseGestures = mouseGestures.map(arr => arr.map(v => { const dist = Math.hypot(vx, vy);//length of vector vx = Math.abs(vx) > minMove && Math.abs(vx) * treshold > dist ? vx : 0; vy = Math.abs(vy) > minMove && Math.abs(vy) * treshold > dist ? vy : 0; return v; }) ).map(arr => arr .map(v => { return { x: Math.sign(vx), y: Math.sign(vy) }; }) .filter(v => Math.hypot(vx, vy) > 0) );
      
      





結果



 gestures.map(gesture => normalizedMouseGestures.mergeMap( moves => Rx.Observable.from(moves) .sequenceEqual(gesture.sequence, comparer) ).filter(x => x).mapTo(gesture.name) ).mergeAll().subscribe(gestureName => actions[gestureName]());
      
      





sequenceEqualを䜿甚するず、受信した動きを元の動きず比范し、䞀臎する堎合は特定のアクションを実行できたす。



GIF




→ ここでゞェスチャヌで遊ぶこずができたす



ゞェスチャ認識に加えお、HTMLキャンバスには初期および正芏化されたマりスの動きの䞡方の描画もありたす。 コヌドの可読性はこれに圱響されたせん。



さらにもう1぀の利点がありたす。Rxストリヌムを䜿甚しお蚘述された機胜は、簡単に远加および拡匵できたす。



たずめ





䟿利なリンク
www.introtorx.com

getakka.net/articles/intro/what-is-akka.html

reactx.io/rxjs/class/es6/Observable.js~Observable.html

reactx.io/languages.html



All Articles