clean architectureで設計してrxjsを使った話
TRANSCRIPT
![Page 1: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/1.jpg)
kondei
twitter: @_kondei
Clean Architectureで設計して RxJSを使った話
![Page 2: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/2.jpg)
何に使ったの?
ニコニコ生放送の、動画広告
…を配信するための、運営ツール
(ユーザーには見えない)
![Page 3: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/3.jpg)
素のTypeScriptで実装するか…
![Page 4: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/4.jpg)
▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂ うわああああああ
![Page 5: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/5.jpg)
要件が複雑
• 機能が多い
• 13個の細かい機能
• 3つのAPIを叩く
• 状態が多い
• 非同期処理が多く、イベント発火の時系列が複雑
• ツールの有効無効の切り替え
• カウントダウン表示 & 終了時に色んな処理
• ツールをクリックしたら10秒おきに動画広告状態取得開始
• 色んな表示を10秒おきに取る動画広告状態に同期
• …
![Page 6: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/6.jpg)
• 非同期処理のリッチなライブラリを使う
• アーキテクチャは何でもいいが、「プレゼンテーションとドメインの分離」原則を適用する
• データの流れと依存関係を単方向にする
• Fluxや、ちゃんとしたMVCのように
• 可能なかぎりReactiveに書く
• Interactiveに操作しだすと、どこから変更されるのか分からない
こうしないと書ける気がしない
![Page 7: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/7.jpg)
何を採用しよう?
![Page 8: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/8.jpg)
RxJS 採用
• jQueryと協調する
• TypeScriptですぐに使えるFRPライブラリ
• IE8対応してる
• 社内で使用実績があった(nicocas)
• (のちのち書いたFRPライブラリ比較)
• http://qiita.com/kondei/items/17e5d4867a0652911e52
![Page 9: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/9.jpg)
クリーンアーキテクチャ 採用
• 厳密に適用すれば、依存関係は一方向になる
• DDDを適用したほうが機能拡張に強いはず
• サーバサイドはDDDだし…
http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
![Page 10: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/10.jpg)
設計
![Page 11: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/11.jpg)
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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/12.jpg)
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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/13.jpg)
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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/14.jpg)
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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/15.jpg)
Presenter は何をしてるの
1. Controller や Usecase らの Observable を入力
2. ストリーム(イベント)を操作・合成
3. Subscribe 内で DOM 操作
表示要素が、ユースケースにReactする
![Page 16: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/16.jpg)
どういう流れでストリーム構築するの
1. TypeScriptだったので、全てconstructorで構築
要するに、ストリームを1度だけ宣言することが重要
2. 後述のInitializerで、依存関係の無い順に初期化
![Page 17: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/17.jpg)
こんな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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/18.jpg)
実装後しばらくして気づいた
![Page 19: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/19.jpg)
MVI アーキテクチャに似てた
Model
Intent View
入力: Modelからのデータイベント 責務: プレゼンテーションロジック出力: Modelのレンダリングや、生のユーザー入力イベント
入力: Intentからのユーザーインタラクションイベント 責務: ビジネスロジック 出力: データイベント
入力: Viewからの生のユーザー入力イベント 出力: Modelフレンドリーなユーザーインタラクションイベント(Observable)
![Page 20: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/20.jpg)
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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/21.jpg)
クリーンアーキテクチャ 感想
今回の自分の設計では…
• RxJS とうまく協調できた
• MVIに近いものになり、その方が単純化できただろう
• 設計力不足でドメインモデル貧血症になった
• フロントエンドでは、動画広告というドメインモデルがフラグ1個と同一性判定しか持たなかった
• 動画広告案件のユビキタス言語も決めたが、ほぼサーバ側でしか使わなかった
• この規模で依存関係の逆転は過度の抽象化だった
![Page 22: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/22.jpg)
心残り
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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/23.jpg)
RxJS だけの話
![Page 24: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/24.jpg)
RxJS 気をつけたこと
• なるべくストリームの操作で済ませる
• Subscribe を書く場所を絞る(今回は基本的にPresenter)
• Subscribe 内で Subscribe しない
• 副作用はSubscribeに書き、Doは極力避ける
• ストリームの途中で副作用を書くと、分岐したら何の原因で副作用が発生するかわからなくなる
![Page 25: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/25.jpg)
RxJS 気をつけたこと
• Subjectをなるべく使わない。使える用途は、
• テスト用
• 非Observable → Observableのラッパ作るとき
• 何らかの事情でObservableを拡張したいとき
• (とはいえpublish/multicast等が似たようなことしているので必要ないかも)
• 子ストリームの結果を親へ流したくて、副作用でデータを受け付けて流すストリームを提供したいとき
![Page 26: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/26.jpg)
RxJS 気をつけたこと
• OnNextを避ける
• 明示的に書くと手続き型になるため
• 手続き型に書いてるなら使ってもいいかも(ただしその場合はクラス内に隠蔽し、外に公開するならasObservableをかませる)
![Page 27: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/27.jpg)
RxJS 気をつけたこと
• Observable を公開するときはHot変換しておく
• Coldのまま公開して分岐すると、Subscribe のたびにそのObservable内の処理が走る
![Page 28: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/28.jpg)
RxJS 気をつけたこと
• 知見の共有!
• 使い手が少ないとレビュー依頼にも困る
• slackの #rx 活用
• 社内ConfluenceにRxの知見まとめページを作った
• 他、Qiitaに記事を上げたりなど
![Page 29: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/29.jpg)
RxJS ハマったこと
• 現象
• APIリクエストを含むストリームを関数で定義して公開したら、Subscribeの回数分APIリクエストが走った
• 理由
• Subscribeのたびに関数が呼ばれ、ストリームが構築され、APIリクエストも走った
• 対策
• ストリームはコンストラクタで定義してローカルメンバ変数に代入しておき、それを返すだけの関数を提供すべし
![Page 30: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/30.jpg)
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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/31.jpg)
RxJS ハマったこと
• 現象
• just(jQuery.val()) を返す関数を作ったら、undefined が流れてきた
• 理由
• just でストリーム作るタイミングで jQuery.val()がundefined だった
• 対策
• defer(() => just(JQuery.val())) のように、遅延評価
![Page 32: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/32.jpg)
RxJS ハマったこと
• 現象
• 定期的な fromPromise を起点にした処理が、IEでは2度目のリクエスト以降動作しなかった
• 理由
• IE は Ajax の GET をキャッシュするせいで、2度目以降はストリームが発火しない
• 対策
• cache: false 追加
• http://qiita.com/kondei/items/99648a07418793d5f50b
![Page 33: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/33.jpg)
RxJS ハマったこと
• 現象
• IE8でエラー
• 理由
• IE8の予約語に「do」があり、do()と重複
• 対策
• RxJSのGitHubドキュメント見て、別名メソッドに変える
• do → tap
• catch → catchException
![Page 34: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/34.jpg)
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を使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/35.jpg)
RxJS 使わなかったら?
• 独自イベントの乱舞
• コールバック地獄
![Page 36: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/36.jpg)
総括
• RxJS と Clean Architecture を良く協調させれた
• RxJS を全面的に使う際のアーキテクチャは、大抵はMVIの方が単純で良い
• RxJS は罠が多いので知見を共有すべし
![Page 37: Clean Architectureで設計してRxJSを使った話](https://reader031.vdocuments.site/reader031/viewer/2022020116/55a640b81a28ab433a8b46d1/html5/thumbnails/37.jpg)
リンク
• クリーンアーキテクチャ 初出記事
• http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
• クリーンアーキテクチャについて書いた記事
• http://qiita.com/kondei/items/41c28674c1bfd4156186
• RxJS について書いた記事
• http://qiita.com/kondei/items/99648a07418793d5f50b
• http://qiita.com/kondei/items/17e5d4867a0652911e52