clean architectureで設計してrxjsを使った話

37
kondei twitter: @_kondei Clean Architectureで設計して RxJSを使った話

Upload: kondei

Post on 15-Jul-2015

3.195 views

Category:

Engineering


4 download

TRANSCRIPT

Page 1: Clean Architectureで設計してRxJSを使った話

kondei

twitter: @_kondei

Clean Architectureで設計して RxJSを使った話

Page 2: Clean Architectureで設計してRxJSを使った話

何に使ったの?

ニコニコ生放送の、動画広告

…を配信するための、運営ツール

(ユーザーには見えない)

Page 3: Clean Architectureで設計してRxJSを使った話

素のTypeScriptで実装するか…

Page 4: Clean Architectureで設計してRxJSを使った話

▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂ うわああああああ

Page 5: Clean Architectureで設計してRxJSを使った話

要件が複雑

• 機能が多い

• 13個の細かい機能

• 3つのAPIを叩く

• 状態が多い

• 非同期処理が多く、イベント発火の時系列が複雑

• ツールの有効無効の切り替え

• カウントダウン表示 & 終了時に色んな処理

• ツールをクリックしたら10秒おきに動画広告状態取得開始

• 色んな表示を10秒おきに取る動画広告状態に同期

• …

Page 6: Clean Architectureで設計してRxJSを使った話

• 非同期処理のリッチなライブラリを使う

• アーキテクチャは何でもいいが、「プレゼンテーションとドメインの分離」原則を適用する

• データの流れと依存関係を単方向にする

• Fluxや、ちゃんとしたMVCのように

• 可能なかぎりReactiveに書く

• Interactiveに操作しだすと、どこから変更されるのか分からない

こうしないと書ける気がしない

Page 7: Clean Architectureで設計してRxJSを使った話

何を採用しよう?

Page 8: Clean Architectureで設計してRxJSを使った話

RxJS 採用

• jQueryと協調する

• TypeScriptですぐに使えるFRPライブラリ

• IE8対応してる

• 社内で使用実績があった(nicocas)

• (のちのち書いたFRPライブラリ比較)

• http://qiita.com/kondei/items/17e5d4867a0652911e52

Page 10: Clean Architectureで設計してRxJSを使った話

設計

Page 11: Clean Architectureで設計してRxJSを使った話

Controller Gateway Presenter

Domain Model

Usecase

View (html)

API

<I> Gateway

<I> Controller

External Interface

Interface Adapters

Application Business Rules

Enterprise Business Rules

Page 12: Clean Architectureで設計してRxJSを使った話

Controller Gateway Presenter

Domain Model

Usecase

View (html)

API

<I> Gateway

<I> Controller

External Interface

Interface Adapters

Application Business Rules

Enterprise Business Rules

入力イベントをObservableとして公開

APIレスポンスをObservableとして公開

Page 13: Clean Architectureで設計してRxJSを使った話

Controller Gateway Presenter

Domain Model

Usecase

View (html)

API

<I> Gateway

<I> Controller

External Interface

Interface Adapters

Application Business Rules

Enterprise Business Rules

入力イベントをObservableとして公開

APIレスポンスをObservableとして公開

Domain Modelや入出力を利用して ユースケースのドメインロジックを実現し、

Observableとして公開

Page 14: Clean Architectureで設計してRxJSを使った話

Controller Gateway Presenter

Domain Model

Usecase

View (html)

API

<I> Gateway

<I> Controller

External Interface

Interface Adapters

Application Business Rules

Enterprise Business Rules

入力イベントをObservableとして公開

APIレスポンスをObservableとして公開

Domain Modelや入出力を利用して ユースケースのドメインロジックを実現し、

Observableとして公開

UsecaseやControllerの Observableを利用して

プレゼンテーションロジックを実現

Page 15: Clean Architectureで設計してRxJSを使った話

Presenter は何をしてるの

1. Controller や Usecase らの Observable を入力

2. ストリーム(イベント)を操作・合成

3. Subscribe 内で DOM 操作

表示要素が、ユースケースにReactする

Page 16: Clean Architectureで設計してRxJSを使った話

どういう流れでストリーム構築するの

1. TypeScriptだったので、全てconstructorで構築

要するに、ストリームを1度だけ宣言することが重要

2. 後述のInitializerで、依存関係の無い順に初期化

Page 17: Clean Architectureで設計してRxJSを使った話

こんなInitializerをHTMLから呼んだ

export class Initializer { constructor(private apiUrl: string, private $hogeElement: JQuery) { // 入力 var controller = new Controller($hogeElement); var apiClient = new ApiClient(apiUrl); // ユースケース var usecase1 = new Usecase1(controller, apiClient); var usecase2 = new Usecase2(controller, apiClient); var usecase3 = new Usecase3(controller); // 出力 var presenter1 = new Presenter1(controller, usecase1, …); var presenter2 = new Presenter2(controller, usecase1, …); } }

Page 18: Clean Architectureで設計してRxJSを使った話

実装後しばらくして気づいた

Page 19: Clean Architectureで設計してRxJSを使った話

MVI アーキテクチャに似てた

Model

Intent View

入力: Modelからのデータイベント 責務: プレゼンテーションロジック出力: Modelのレンダリングや、生のユーザー入力イベント

入力: Intentからのユーザーインタラクションイベント 責務: ビジネスロジック 出力: データイベント

入力: Viewからの生のユーザー入力イベント 出力: Modelフレンドリーなユーザーインタラクションイベント(Observable)

Page 20: Clean Architectureで設計してRxJSを使った話

MVI とは

• 完全リアクティブなMVC

• 提唱者によるフレームワークがある

• staltz/Cycle.js

• RxJSを活用している

• Flux/Reactで使ってるEvent emitterは単機能すぎるという意見らしい

http://futurice.com/blog/reactive-mvc-and-the-virtual-dom/

Page 21: Clean Architectureで設計してRxJSを使った話

クリーンアーキテクチャ 感想

今回の自分の設計では…

• RxJS とうまく協調できた

• MVIに近いものになり、その方が単純化できただろう

• 設計力不足でドメインモデル貧血症になった

• フロントエンドでは、動画広告というドメインモデルがフラグ1個と同一性判定しか持たなかった

• 動画広告案件のユビキタス言語も決めたが、ほぼサーバ側でしか使わなかった

• この規模で依存関係の逆転は過度の抽象化だった

Page 22: Clean Architectureで設計してRxJSを使った話

心残り

jQueryでDOM操作した部分がInteractive感ある

• MVVM のようなデータバインドでReactiveにできそう

• http://webrxjs.org/ というRxJSのMVVMフレームワークもある

• Cycle.jsでJSXで宣言的にDOM操作する例あり

• https://github.com/ivan-kleshnin/cyclejs-examples

Page 23: Clean Architectureで設計してRxJSを使った話

RxJS だけの話

Page 24: Clean Architectureで設計してRxJSを使った話

RxJS 気をつけたこと

• なるべくストリームの操作で済ませる

• Subscribe を書く場所を絞る(今回は基本的にPresenter)

• Subscribe 内で Subscribe しない

• 副作用はSubscribeに書き、Doは極力避ける

• ストリームの途中で副作用を書くと、分岐したら何の原因で副作用が発生するかわからなくなる

Page 25: Clean Architectureで設計してRxJSを使った話

RxJS 気をつけたこと

• Subjectをなるべく使わない。使える用途は、

• テスト用

• 非Observable → Observableのラッパ作るとき

• 何らかの事情でObservableを拡張したいとき

• (とはいえpublish/multicast等が似たようなことしているので必要ないかも)

• 子ストリームの結果を親へ流したくて、副作用でデータを受け付けて流すストリームを提供したいとき

Page 26: Clean Architectureで設計してRxJSを使った話

RxJS 気をつけたこと

• OnNextを避ける

• 明示的に書くと手続き型になるため

• 手続き型に書いてるなら使ってもいいかも(ただしその場合はクラス内に隠蔽し、外に公開するならasObservableをかませる)

Page 27: Clean Architectureで設計してRxJSを使った話

RxJS 気をつけたこと

• Observable を公開するときはHot変換しておく

• Coldのまま公開して分岐すると、Subscribe のたびにそのObservable内の処理が走る

Page 28: Clean Architectureで設計してRxJSを使った話

RxJS 気をつけたこと

• 知見の共有!

• 使い手が少ないとレビュー依頼にも困る

• slackの #rx 活用

• 社内ConfluenceにRxの知見まとめページを作った

• 他、Qiitaに記事を上げたりなど

Page 29: Clean Architectureで設計してRxJSを使った話

RxJS ハマったこと

• 現象

• APIリクエストを含むストリームを関数で定義して公開したら、Subscribeの回数分APIリクエストが走った

• 理由

• Subscribeのたびに関数が呼ばれ、ストリームが構築され、APIリクエストも走った

• 対策

• ストリームはコンストラクタで定義してローカルメンバ変数に代入しておき、それを返すだけの関数を提供すべし

Page 30: Clean Architectureで設計してRxJSを使った話

RxJS ハマったこと

• 現象

• Cold Observableを Hot変換してrepeat()したら、Cold Observable の終了後に Call stack overflow

• 理由

• 終了した Cold が Completed を流し、repeat()が再Subscribeする、を無限に再帰

• 対策

• Cold ObservableをHot変換したらrepeat()しない

• Rx の仕様かも?

Special Thanks: wilfrem さん、toRisouP さん

Page 31: Clean Architectureで設計してRxJSを使った話

RxJS ハマったこと

• 現象

• just(jQuery.val()) を返す関数を作ったら、undefined が流れてきた

• 理由

• just でストリーム作るタイミングで jQuery.val()がundefined だった

• 対策

• defer(() => just(JQuery.val())) のように、遅延評価

Page 32: Clean Architectureで設計してRxJSを使った話

RxJS ハマったこと

• 現象

• 定期的な fromPromise を起点にした処理が、IEでは2度目のリクエスト以降動作しなかった

• 理由

• IE は Ajax の GET をキャッシュするせいで、2度目以降はストリームが発火しない

• 対策

• cache: false 追加

• http://qiita.com/kondei/items/99648a07418793d5f50b

Page 33: Clean Architectureで設計してRxJSを使った話

RxJS ハマったこと

• 現象

• IE8でエラー

• 理由

• IE8の予約語に「do」があり、do()と重複

• 対策

• RxJSのGitHubドキュメント見て、別名メソッドに変える

• do → tap

• catch → catchException

Page 34: Clean Architectureで設計してRxJSを使った話

RxJS 使って何が良かった?

• 複雑な処理を、他の複数の処理の起点にできた

• Observableの公開・合成 最高に便利!

• 素早く機能追加できた

• ストリームに関数1個挟むだけだったり

• 時間の概念を含む実装が楽だった

• 定期的リクエスト

• 複数の終了条件を持つカウントダウンも簡単に

private countdownStream(remain: number) { Rx.Observable .timer(0, this.countdownSpan) .map(r => { return remain - r + 1; }).takeWhile(r => { // 0秒で終了 return r >= -1; }).takeUntil(動画広告状態が非再生中); }

Page 35: Clean Architectureで設計してRxJSを使った話

RxJS 使わなかったら?

• 独自イベントの乱舞

• コールバック地獄

Page 36: Clean Architectureで設計してRxJSを使った話

総括

• RxJS と Clean Architecture を良く協調させれた

• RxJS を全面的に使う際のアーキテクチャは、大抵はMVIの方が単純で良い

• RxJS は罠が多いので知見を共有すべし