前置き
フロントエンドを開発する場合、なんとなくでFluxデザインパターンを実装したライブラリを使うことが良くあります。
それらなんとなく使う人の動機は大抵 なんかイケてる感があるのでとりあえず使いたい でしょう。
そのせいでFluxデザインパターンを正しく理解しないで使った場合には当然のように、データが単方向に流れて幸せになるのではなく、無駄にでかい機構で出来たグローバル変数を使って、著しく生産性を下げ、技術的負債は溜まっていくでしょう。
今回はそれらの悩みを感じ始めた方に対しての Fluxデザインパターン を正しく理解するのを目的とします。
また、その上でVue.jsとVuexの結合パターンの1つを提案出来たらと思います。
※ 私はVue.jsとVuexでアプリを開発しています。React Reduxで業務をしたことがありません。それを前提とした上での話になります。
※ 2018年6月2日現在、こちらのデザインパターンのVue.jsとVuexのサンプルコードは掲載していません。あくまで設計の部分の話だけしています。近いうちに満足行くサンプルが作成できたら、そちらを具体例として解説する予定です。
追記
Twitterで少し上がっていたVue.jsやVuexと関係ないという話に対してここで少し言及します。
タグを付けていた理由としては、Vue.jsとVuexの初心者、中級者の方に最も見てほしいという気持ちがあったからです。それは今でも変わりません。確かにサンプルコードは記載していないので、パッと見は関係性は薄く見えるかもしれませんが、Vue.jsとVuexの間に1つViewModelをMixinとして用意し、2way bindingする設計、アプローチはとてもVue.jsとVuexを組み合わせて使っているコーダーのエクスペリエンスとコード品質を向上させると私は信じています。繰り返されますが、私はこれをVue.jsとVuexを使ってる方に最も伝えたかったので、Vue.jsとVuexのタグを付けています。
私はVue.jsとVuexを使っている方にこそ、このFluxを正しく理解してほしいと思っています。なぜなら、Vue.jsとVuexは結合度が高すぎて、学習していない状態でもとりあえず使えてしまうことにより、本来するべきVue.jsだけで開発した際の失敗の経験をしないで、なんかもっと良い使いないかなぁ、という漠然の思いだけを持ってしまって迷子になっている方が多いと思っています。自身が書いているコード上の各データの役割や置き場、変更する際の方法、そのデータの種類をしっかりと回答できない人の助けに少しでもなればと思い、記載しました。(筆者自身がそうでしたので。
また、Vue.jsとVuexを使っていない方でもフロントや何か参考になればと思います。
ゴール
Fluxに対する理解と、付き合い方をわかるところまで。
Fluxとは
Fluxは、アプリケーションのデータフローを管理するためのパターンです。最も重要な概念は、データが一方向に流れることです。
ソースはこちらです
今回はこのソースと自らの経験則やDDD等の知識を合わせてわかりやすく重要な箇所を説明してみますが、ソースを確認した方がより確実な情報が得られることは確かです。
Fluxを構成部品
Dispatcher
DispatcherはActionを受け取り、Dispatcherに登録したStoreに配信します。すべてのStoreがあらゆるActionを受け取ります。 Storeは複数存在しますが、シングルトンDispatcherは1つだけの存在である必要があります。
シングルトンDispatcherが1つだけである事を実現する事により、全てのActionが必ず単一Dispatcherを経由する事が保証されます。
Store
Storeは、アプリケーションのデータを保持するものです。Storeは、アプリケーションのDispatcherに登録され、Actionを受け取ることができます。Store内のデータは、Actionに応答することによってのみ変更する必要があります。StoreにはPublic Setterはなく、Getterだけが用意されます。アプリケーションとStoreの関係は1対多です。
Storeのデータが変更されるたびに、「change」イベントを発生する必要があります。Viewはこのchangeイベントに対してイベントリスナーを登録する事により、Storeの変化に対して適切な処理を実行できます。
Action
Actionは、自身が利用するインターフェイスを定義します(外部APIを実行するApiUtils含む)。Actionは実行された際にどのような変化をStoreに与えるのかをアトミックに定義します。
ビジネスロジックを置くのに適しています。
View
StoreからのデータがViewに表示されます。Viewはあなたが望むどんなフレームワークでも使用できます(Vue.jsでもReactでも好きなのをどうぞ)。ViewがStoreのデータを使用する場合は、そのStoreのイベントを変更するために購読する必要があります。次に、Storeが変更を発行すると、Viewは新しいデータを取得して再レンダリングできます。Actionは、通常、ユーザーがアプリケーションのインターフェースの一部と対話するときにViewから送出されます。
代表的な例外な例としては、ページ本体を構成する最上位コンポーネントを構成する際に必要とする初期化Actionでしょうか。
フロントを構成する2大要素
そもそもの話になりますが、アプリケーションを作成する上で何かしらの業務をソフトウェアを利用して解決するのが目的でしょう。
ですので、画面上に描画される情報は大体業務に関わる知識とそのアプリケーションを操作するインターフェイスぐらいでしょう。それらは以下の2つの要素で実現可能です。
View
見た目を構築します
Model
業務に関する知識の情報とのやり取りをします
作成単位は業務知識単位と一致します
この2つの要素がフロント側に用意されていれば、密結合にすればアプリケーションは構築できますが、成長させる前提のあるアプリケーションや、大規模アプリケーションを構築する場合だと、それだととても大変でしょう。
FacebookではViewの各画面や表示領域によって、Modelを密結合に使っていたせいで、バグの解析が困難になり、またテストがしづらく、バグが発生しやすくなりました。この話はとても有名だと思います。
とてもではないですが、Viewに散在しているModelを追いきって管理し続けれるとは思えません。
その際に助けとなるのが、Fluxデザインパターンです。
これでしたら、Viewに散在し、Viewに内包されていた各Modelの実行条件等を一元管理でき、複雑度の低下、メンテナンス性の向上を見込めるでしょう。
Viewはただ、加工されたStoreの値を表示し、フォーム等の値をActionに適した形式に加工して、Actionを発行する事に集中できます。
あれ、StoreとViewのやり取りにViewModelのパターンはとても適していると思いませんか?そうすれば、ActionとStoreはアプリケーションのデータに集中し、Viewの知識は一切流入しません。同様にViewにもAction, Storeの知識は一切流入しません。仲介役はViewModelです。Viewにあった複数責任が分解でき、よりシンプルになりました。せっかくですのでこれら4つの要素でパターンを作成してみます。
パターン図
責務
View
UIに責務があります。見た目とそれらが操作された際のイベントの送出を含みます。また、UI自身の状態や、子UIの相対的な関係も全て担当します。正しくデータが表示され、正しく操作に対するメソッドの実行が責任です。
Action
ビジネスロジックとのやり取りに責務を持ちます。
Store
ビジネスロジックから取得したデータのみの保管に責務をもちます。
ViewModel
特定のViewに依存する
Viewからの入力をActionが扱える形式に整形する
Storeの値をViewが扱える形式に整形する
Tips
今後追加していく予定です!
Storeの作成単位のコツ
StoreはDDDの概念の1つであるドメインモデル単位で作成するととてもうまくいくと思います。
追記: ドメインモデル単位での作成も良いのですが、各画面に対して表示する情報や関係性の差があるため、ドメインモデル単位だけでは辛い部分もあります。
firebase storeやGrpc等を使えば話は違いますが、一般的なサーバーでapi作って、フロントで表示構成だと辛くなってきます。
認証済みユーザーに関するドメインなどは、文脈が固定されているのでとても上手く機能しますが、画面単位での文脈の差がでてくるアプリケーションの場合は、画面単位で作成すると、うまくいくと思われます。
最後に
如何だったでしょうか。
最後の図は大分雑ですが、ViewModelを挟むことにより、ViewとStoreの境界が大分明確になったかと思います。
私としてはこのViewModelをVue.jsのMixinとして作成すると、とてもVue.jsとVuexのクッションにしやすくて良いと思っています。
また、Viewを感覚ではなくきちんと言語化して一定品質での開発をする上で私は、アトミックデザインをとてもおすすめしています。
補足
-
Vuexのファイル構成はducksパターンで作られてるように見受けられます。
vuexでのducksパターンとは state action mutation constant を module というファイルにまとめてしまいます。
どのみち密結合になっているので1ファイルにまとめたほうが見通しが良く管理しやすいという理由です。 -
ViewModelはどうでしょうと提唱していましたが、ViewControllerが既にFluxだと利用するのを推奨されていて、Fluxの登場人物との関連性のあるViewはViewControllerに昇格し、データを子Viewにprops downする様です。
ViewControllerをなくし、ViewとViewModelに切り分け、View単体でも動作しやすいようにできる設計の方がテストもしやすく、密結合で使いやすいですが、速度重視の開発ならば、ViewControllerはとても強力で、pageとpageの持つ各セクション単位のみがViewControllerになりうるなど、ViewControllerの作成単位を意識すれば、これはとても強力な概念となりうるでしょう。