昨日の🍺のあとに考えていた雑感。フロントエンドから見たGUIの状態を個人の主観をもとに整理しています。
背景
複雑なJavaScriptアプリケーションを考えながら作る話
と
id:Pasta-K がReact + Canvasで色々やっていた話
などなど。
Reactなどの現代フロントエンドはたいへんしんどいGUIであるという前提もあります。
GUIの状態の種類
人間
諸悪の根源。
だいたいこいつが状態を変更してくる。バリデーションが必要なのもこいつのせい。
わかりやすいUIを提供しないと人間が弱る。
View
Viewの中の状態。UI上の選択状態とか。
Viewに状態を持ちたくない宗派としては、できれば非永続化層(メインのビジネスロジックと状態を持った層)か永続化層(DB)にすべての状態を持っていて欲しいが、パフォーマンス上の問題でthis.stateに状態を持つことはありえる。ReactとCanvasでやっていくにはここに状態を持ってもパフォーマンスが厳しいとかなんとか。
コンポーネント単位でのユニットテストは書けるが、非永続化層よりはめんどくさい。結合テストはもっとめんどくさい。
状態がUIコンポーネントの中に閉じている、パフォーマンス上仕方ないといった条件が揃ったらここに状態を持つ設計を選択したい。
非永続化層
main()にあたる我々がよくいじっているレイヤ。メモリの中の永続化されていない状態とロジックを持つ。PDS(Presentation Domain Separation)を推し進めると、ここと永続化層ですべての状態を管理することになる。
FluxにおけるStoreとかStateとか言ってるやつがここ。Fluxの場合はImmutableであると扱いやすいと思う。適切に責務ベースでドメインモデルを分割していればテストも楽。
永続化層から取り出した状態、サーバへ投げるための状態、UIの状態などもここに突っ込まれるので、ちゃんと設計しないと簡単にカオスになる層。
たぶん低〜中程度の複雑さのアプリなら永続化層を扱わなくてもなんとかスケールできると思う。複雑度が高くなると状態をいったん永続化層に逃して正しくCQRS(コマンドクエリ責務分離)したほうが楽になるはず。*1
永続化層
サーバ屋的にはDB. インメモリなDBであることもある。ブラウザでなければファイルでもよい。高級なDBであるほどトランザクション処理に対応してたりしてとても便利。フロントエンド環境ではインメモリDBが主な対象。よくシングルトンなMapオブジェクトで実装されているような気がするが、欲を言うとブラウザ内にACIDなDBがほしい。IndexedDBは使ったことないので何も言えません。
ここの層もちゃんとデータ構造を設計しないとスケールしない。永続化層へのデータ挿入は非永続化層にいるロジックたちの責務なので、テストはロジックを通して行われる。
サーバ
ネットワーク越しにつながれた状態で、非永続化層から適切なタイミングで状態を反映してやる必要がある。
フロントエンドで非同期処理といったらだいたい半分がこれ。役割とかについては特に説明不要でしょう。
図ではサーバへの状態の反映しか描かれてないが、サーバから状態を取り出すこともある。どんなリクエストを送ったとしても、レスポンスの結果を切り出して非永続化層の状態とマージすることが多い。また、人間による状態の変更をブロックするためにリクエスト中であることを示す状態を必要とすることもある。
各アプローチ
人間 -> View, 非永続化層のループ
フロントエンドGUIはブラウザという環境の制約からここに閉じることが多い。FluxはこのループにCQRS的な発想を導入したものだと思ってる。上でも述べたようにサーバが登場したり状態が複雑になるとこれらの層だけではスケールしなくなることがある。
this.stateやReduxは永続化層を扱うことを想定していないし、扱うのも直接は難しい気がする。
上図全部のスタック
複雑なJavaScriptアプリケーションを考えながら作る話 が対象としているもの。おそらくネイティブアプリと基本的な構成は変わらない?
その他
状態を変更するのは誰か
ふつうのFluxだと人間による状態の変更を特別扱いしている問題がある。
Fluxの一派?であるCycle.jsはその点ぶっ飛んだ発想をしていて、main()ロジック以外のすべてが状態を変更してくると言っている。Cycle.jsはRxでだいたいすべてのロジックを実装しろという恐ろしいフレームワークだが、誰が状態を変更するかという問いに対してはおそらく正しいことを言っていて、たしかに我々が書くmain()の状態を変更してくるやつはユーザ操作もサーバとの通信も平等に作用(action)である。
だから、Reduxではユーザ操作以外の作用をミドルウェアで解決するしかないのであり、Redux単体では特別扱いしたユーザ操作作用の問題解決のみに注力しているのだと私は思っている。
そしてユーザ操作作用以外の作用が登場すると、それは競合状態のはじまりであり、作用と作用の間でどちらを優先するかという問題が出てきたりするがそれはまた別のはなし……。*2
どの設計方針を選択するか
所属する組織の特徴*3と要件をよく観察して考えて各位で判断するしかない。やっぱり銀の弾丸はないし、現実に合った選択を都度やっていくしかない。流行りものも上記の問題点がいくつかあり、場合によっては合う合わないがある。
おまけ
上で概念を説明した図に登場するハッカドール3号くんです。