Reagentを使うとクライアント側で独立したSPAは実装できますが、サーバーとのデータ通信や、Reactのstateとpropsを抽象化してくれるratomの状態をサーバーとどうやって同期をするのかが問題です。クライアントだけで完結するアプリなら良いですが、多くのアプリはデータを永続化するためのバックエンドに何らかのデータベースが必要になります。StackOverflowに興味深い議論があったのでいくつか設計のパターンを調べました。
- Keeping Client State Up-To-Date In Reagent / Clojurescript
- Server push of data from Clojure to ClojureScript
データベースの同期
ClojureScriptにはDataScriptというDatomicのクライアント版のようなインメモリのデータベースがあります。クライアント側にもデータベースを持つアーキテクチャには、Couchbase Sync GatewayのCouchbase LiteやMeteorのminimongoがあります。後者2つはクライアントのデータベースの状態はにautomagicallyな仕組みでサーバーと同期されますが、DataScriptの場合は同期の仕組みを作る必要がありそうです。
とても便利そうなのですが、ちょっと複雑になりそうで手がでません。
WebSocketとAjax (REST)
ちょうどHapi.jsやactionhero.jsでREST APIとWebSocketを一つのサーバーで提供する設計を試しています。どちらのプロトコルも用途にあわせて使い分けができるようにしたいです。
WebSocket、Ajax、core.asyncのすべてに対応するライブラリが増えています。
LuminusはClojureScriptを使う場合にReagentを推奨しています。Reagentからサーバへcljs-ajaxを使ってAjax通信をします。おそらく多くのSPAの実装パターンがこれに該当すると思います。
REST APIを提供するライブラリはLiberatorを採用することが多いです。
core.async
まだcore.asyncについて理解できていないのですが、ClojurScriptとClojureの間をcore.asyncで通信できるようです。
RPC
HoplonのサーバーサイドはCastraのRPCライブラリを使います。クライアント側からmkremote
関数を使ってサーバー側defrpc
関数を実行するようです。
Comparing ReagentにReagentとの比較があります。HoplonはClojur/ClojureScriptを使いやすく統合するためにBootを採用してビルドや開発環境が充実しています。
フルスタック
Clojure / ClojureScriptのフルスタックフレームワークを調べてみました。Reagentに対応していない場合もあります。
Sente: WebSocket / Ajax
Reagent: React
Bootstrap: CSS
H2: relational database
http-kit: async http server
Figwheel : liver reload
Reagent: React
DataScript: client side database
Bootstrap: CSS
Selmer: template engine
Ring: http server
Compojure: routing
Liberator: request flow
Enlive: template
Quiescent: React
Figwheel : liver reload
DataScript: client side database
H2: relational database
http-kit: async http server
Aleph: async http server
Sente: WebSocket / Ajax
Monger: MongoDB
Datomic: Immutable database
Figwheel : liver reload
Weasel: REPL
Om: React
Ring: http server
http-kit : async http server
jetty7-websockets-async: async http server
まとめ
Keeping Client State Up-To-Date In Reagent / Clojurescriptの議論で気にいったパターンはSPAをデータベースのクライアントの1つとみなすデザインです。
処理に必要なデータはスナップショットとして最初にクライアントに取得します。SPA側でデータを加工したあとにサーバーにsubmitします。SPAが対象のデータをリモートで処理している間の一貫性は保証されますが、処理中に他のクライアントがサーバーのデータ更新をコミットしていればsubmitに失敗して操作をやり直します。
これは昔からあるアーキテクチャでリッチクライアントと呼ばれていたFlexやGWT、ExtJSなどでよく実装していた手法です。業務系アプリだと意外とうまくいきます。同じレコードを並行して更新させないようにしたり、頻繁にデータを更新しないなど、仕様を工夫するのが現実的なようです。