話の前提
ここだけの定義
- クライアントとはWeb/Android/iOS/CLI/管理画面を指す
- この話の中でGAEのDataStoreとMemcacheを同一のものとみなす。なぜなら多くの場合その2つを透過的に扱うライブラリを使うから
- ローカルキャッシュを全てIndexedDBとひとくくりにする。AndroidのSQLiteなどは適宜読み替えてほしい。また、サーバーから取ってきたデータは全て一度IndexedDBに入れることを想定している。
- とりあえずReduxの話をする。適宜読み替えてほしい。
- RESTFul APIの全体をGETした時の戻り値を、とりあえずRESTFul API全体と呼ぶ。
- DBは今の所特定の実装の話をしていない。ただ、GAEのDataStoreを意識している。
- ReduxのStateに入れるデータにはサーバー起源データとユーザー起原データがあると思う。掲示板アプリなら「ユーザー一覧」「書き込み一覧」などがサーバー起源データだ。「キーボードから入力中のテキスト」「現在開いている板」などがユーザー起源データだ。
作ろうと思っているフレームワーク
作ろうと思っているフレームワークはこういうものだ。
ユーザーがすること
- DBの全データをRESTFul APIの全体に変換する関数を書く
- RESTFul APIの全体をIndexedDBの全データに変換する関数を書く
- IndexedDBの全データをReduxのStateに変換する関数を書く
- 通常のmapStateToProps関数を書く
- RESTFul APIのPUT・PATCH・DELETE・POSTがDBのどの部分を更新するのか関数で定義する
フレームワークがすること
- ビルド時に上記のコードを読みキワッキワに最適化した処理を自動生成する
思い立った背景
今、サーバーサイドをGAE/Go + Gin、フロントエンドをReactで開発している。
現代的なプロダクトは、サーバーとクライアントの双方が大きなモデル層を持つようになった。どちらかがもう一方に依存しなくなったということだ。昔のフロントエンドはサーバーサイドプログラムの携帯ストラップのような扱いだったが、スマホの時代になったら変わった。今ではサーバーとクライアントは完全に粗結合になっている。
また、GinとReactの共通点として、両方とも薄いライブラリであることを強調している。流れの早い業界の中で、「その気になればいつでも捨てられる」ということがアピールポイントになっている。これは現代的なフレームワーク全体に共通していると思う。
このように「各レイヤーが自立して動くようになったことで粗結合になったこと」「薄いライブラリが流行っていること」がプロダクトの健全性に大きく貢献している。しかし、その分、コード量という観点ではプログラマの負担が増えている。トラック一台で運びきれないのでワンボックスを30台用意しましたと言われた気分だ。
今こそ、がっぷり四つの分厚いフレームワークを作るときだ。自分ならできるという自信がある。
自分はずっとサーバーとクライアントの両方を担当してきた。また、サーバーとクライアントの接合部も設計してきた。一人で両方を担当しているが、しっかりと粗結合にしてきた。だから、どのようにサーバーとクライアントを接合するべきかわかっているつもりである。
悪魔のように粗結合に。天使のように密結合に。
根底にある思想
サーバと全てのクライアントはRESTFul APIの型のみに依存するべきだ。その向こう側にどんな処理があるか一切知っていてはいけない。
RDB用語で「関数従属性」という言葉がある。例えばA+B=Cは3つの内2つ値が定まればもう1個の値も定まる。このように全体の内一部がわかればほかも割り出せることを関数従属性があるという。ソースコードにも当てはまる。フロントエンドにこういうコードがあるからサーバーサイドにこういうコードがあるはずだと特定できることがある。これはソースコード間に関数従属性があると言えると思う。その場合、プログラマがコードを書くよりコンピューターにコードを生成させたほうが安全なはずだ。
支柱になる考え
DBの全データからRESTFul API全体に変換する関数のASTを取れば、RESTFul APIの一部をGETするリクエストを処理するためにDBのどのデータを取り出さなければいけないか算出できるはずだ。
ReduxのStateのサーバー起源の部分とIndexedDBは虫食いコピーの関係になるということにしておくといいと思う。IndexedDBに保存されているデータの内必要なものだけを随時Reduxにコピーして使う。
RESTFul API全体とIndexedDB全体も虫食いコピーの関係になる。
キャッシュ戦略はコンピューターが立てたほうが精度が高いはずだ。最も素朴な実装をプログラマが書き、それをもとにコンピューターにキャッシュ戦略を立案・実行させるといいと思う。
コードの自動生成という言葉を使っているが、
を現時点では区別していない。一番合目的なものを適宜選ぶ。ただし、IDE補完との相性を重視する。また、ソースコードを自動生成する場合は生成の冪等性を重視する。つまり、一度生成したソースコードを人間が修正したら不磨の大典と化すことは避ける。
避けること
これはプログラミングが苦手な人のための簡単なフレームワークではない。自力で正確に型定義し、仕様をシンプルな純粋関数で表現でき、ちゃんとテストコードが書ける人しか使えないと思う。
jQueryやWordPressのプラグインにありがちな「環境上のどの変数を参照しているのかわからない、どんな副作用をもたらすのかわからない、でもインストールすれば勝手にいい感じにやってくれる」ようなものではない
少なくとも、
- Cookie
- DBのSchema
- RESTFul APIの型
- 公開するRESTFul のPOST・PUT・DELETE・PATCH
- ReduxのStateの型
- ReactのPropsの型
- IndexedDBの型
は各プログラマが責任を持って完全に支配するべきだと考える。
このフレームワークは何をしてくれるのか
ユーザーがどこかの画面に来たときに、その画面を表示するのに必要なデータをRESTFul API、IndexedDBから自動で取り出してReduxのStateに読み出しておいてくれる処理を生成する。すると、プログラマはStateに必要な全てのデータが入っている前提で処理を書くことが可能になる。
また、各GET APIも自動生成してくれる。DBの全データをAPIの全データに変換する関数があるからプログラマの意図した通りに生成されるはずである。
次に、キャッシュレイヤーを自動生成する。静的ファイルを使うもの、DBに非正規化したデータを保存しておくもの、クライアントのファイルにキャッシュさせておくものなどがあると思う。
さらに、DBの一部分を更新した時に、クライアントのIndexedDBをPushで変えたい。それも自動化できる。