この記事は仮想DOM/Flux Advent Calendar 2015 - Qiita13日目です。
二槽式とは「Viewとロジックを切り離し、それぞれが独立して成立することを目指したアーキテクチャ」をさして呼んでいます。
(これは私が勝手に名付けてるだけなので厳密な定義はないです)
このView部分を「フロントエンドフロントエンド」、ロジック部分を「フロントエンドバックエンド」と呼び、合わせて「二槽式」と呼んでいます。
もう少し具体的にいうと、「Fluxのアクション部分を切り離して、View -> postMessage(JSON) -> onmessage(()=>{}) -> Dispatcherという形式にしたもの」です。
(ここではpostMessageを使っていますが、ある程度独立性が保てるのであれば普通のfunction callでも問題ないと考えています)
なにを解決したいのか?
このアーキテクチャの利点はロジック部分(フロントエンドバックエンド)はView部分(フロントエンドフロントエンド)と疎結合になるため、技術的、チーム的に分断された状態で開発することが容易になります。
具体的にはサーバサイドをScalaで開発している場合にフロントエンドバックエンドにScala.jsを採用すると言った決断を行うやすくなります。
これにより「サーバサイドはできたけど、フロントエンドができてないからリリースできない」、「サーバサイドが忙しいからフロントエンド側だけで完結することをして時間を潰す」ような状態を減らしたり、「サーバサイドエンジニアとフロントエンドエンジニアをバランスをとりつつ採用する」といった状態を緩和できるのではないかと思っています。
なぜ分断するのか?
もちろんscalajs-reactやscalajs-angularを使えば分断することなしに全てをScala.jsで構築することは可能です。
ただ、その場合、UI部分との連携や、フロントエンド側のツールとの親和性といったことを考慮しなければならず、リスクが大きいと思っています。
二槽式を採用すると、フロントエンドバックエンドはフロントエンド特有の事情を考慮することになしに、純粋にロジックのみに集中して開発を行うことができます。
(API通信と、ブラウザストレージと、postMessage、onMessage通信部分くらいしか特有の事情がない)
また、フロントエンド側は今後の流行でツールの選択が変わる可能性も高いため、、フロントエンドフロントエンド側にツール群を集約することで捨てる部分と残す部分を明確化しやくすなると思っています。
更にフロントエンドフロントエンドは基本的にJSONからUIを構築し直す処理のみになるため、テストも行いやすくなると考えています。
他にも、サーバサイドと設計を共有しやすくなるため、設計知見の共有も行いやすくなり、サーバサイド側の人間も開発に参加しやすくなると思っています。
特にメンバー内にDDD等の設計知識があると知見の共有が行いやすく、効果が高いと思っています。
サーバ側とソースを共有するか?
本来二槽式は「フロントエンド側を二槽に分ける」以上の意味はありません。
ただ、「サーバサイドとフロントエンドを同じ言語で開発する」と言うと「サーバとクライアントでソースコードを共有するのか?」という質問を受ける事があります。
これに関しては現状共有するメリットよりデメリットが高いと考えており、共有せずに独立して構築した方がいいと思っています。
(設計の共有だけ行い、ソースは独立させる)
特にScala.jsの場合、「Java部分にさわれない(一部触れるけど、基本無理)」、「コンパイル、デプロイのフローが異なる」、「Scalaの機能のうち、一部使えないものがある」ため、リスクが高いと考えています。
(フロントエンドバックエンドにScala.js以外を採用する場合、ソースの共有を行う選択肢もあるかもしれません)
具体的な処理は?
「そもそもどう書くの?(特にFluxがわからない)」という質問も受けるので、特にフロントエンドバックエンド側の処理をメインに書いてみます。
- (フロントエンドバックエンド)起動したらサーバAPI、ブラウザストレージからデータを読み込む。
- (フロントエンドバックエンド)読み込んだデータを纏めてpostMessageへ渡す。
- (フロントエンドフロントエンド)onMessageでデータを受け取ったら内容に従ってUIを構築する。
- (フロントエンドフロントエンド)ユーザイベントが発生したら内容をpostMessageへ渡す。
- (フロントエンドバックエンド)onMessageでデータを受け取ったら内容に従って処理を行い結果をpostMessageへ渡す。
ちなみに、この方式の場合処理は常に非同期になるため、同期的に処理しなければならないEvent(Clipboard等)は別途対応する必要があります。
このためにpostMessageでなくfunction callを採用してもいいですが、その場合「Scala.js内でJavaScript的な同期処理が継続されているか?」を常に判断する必要が出てくるため考える要素は増えると思っています。
(このくらいはフロントエンドフロントエンド側で処理してしまうか、これだけようにfunction callできる処理を切り出すほうがいいと思う)
Workerを使うのか?
「postMessageを使う」という点で「Workerを使うの?」という質問を受けることもあります。
(もともと依然私が「Workerで分断できるレベルで処理を分ける」という発言をしたことも質問を受ける原因だと思いますが)
これに関してはWorkerの使用は意図しておらず、単純に処理を分断するためのツールとしてpostMessageを使うことを意図しています。
(特に重い処理だけをフロントエンドバックエンドへ持ち出すわけではないので、デバッグが面倒になる以上のメリットはないと思っています)
どういう問題があるのか?
二槽式の欠点としては以下の様なものを想定しています。
- ロジックとViewが分断されることで純粋なコード量が増える。
実装自体はシンプルになるので複雑さは多くならないため受け入れます。
- 処理を常にpostMessageでやり取りするため、規定するメッセージ数が膨大になる。
ここは何らかのFWで解決が必要になると思っています。
- (Scala.jsを使う場合)CPU、ネットワーク、メモリの負荷が増える。
(これはうちの事情ですが)PCブラウザ向けなので現状見えている範囲では誤差レベルの差です。 (そもそもVirtualDOM採用している時点でかなりの負担になっているので)
どこから発想したのか?
もともとはChrome extensionでTypeScript + reactを使う場合のアーキテクチャとして発想しました。
当時、TypeScriptとreactは相性的に同時に使うのが難しく、Chrome extensionのbackground pageをTypeScriptで、その他のUI部分をreactで書いたところが元になっています。
二層式か二槽式か
当初「二層式」のtypoとして「二槽式」と書いていましたが、フロントエンドフロントエンドとフロントエンドバックエンドが独立して回転している様子が「二槽式」のイメージにもあっているので「二槽式」としました。
Webフロントエンドだけのアーキテクチャなのか?
ロジックとView間をメッセージのみでやり取りし動作できる場合、Webフロントエンド以外の場所でも実装することは可能です。
ただ、Webフロントエンドの場合、React + Fluxの登場でこの構成を取るのが容易になっています。
採用したほうがいいのか?
正直オススメしません。
なぜならこのアーキテクチャは以下の様な事情のもとに設計されているためです。
- 社内でScalaエンジニアが多い(sbtのソース読んでる人が数人いたりしてScala側の問題はほぼ解決できる)
- 共有できる設計知見がある(うちの場合はDDD)
- フロントエンドエンジニアに対してScalaエンジニアの数が多い
- 程度Scalaがわかって、Scala.jsで変換後のJSを読めるフロントエンドエンジニアがいる
もし、上のような事情に当てはまる場合、採用する価値が有るかもしれません。