クライアントサイドのモデルとは何か 前編 ~ クライアントサイド MVC の死

前置き

この記事、本来は Flux には Model がないのではないかと思った覚書 - ナカザンドットネットFlux の Store が ViewModel かって話からの MVW とかどうでもいいって話 - 猫型の蓄音機は 1 分間に 45 回にゃあと鳴く のアンサーとして書き始めた記事だが、前置きだけで別テーマとなったので、前後編に分割する。

僕は元々がゲームクライアント屋だったときの発想を引きずってるのと、既存の Web の開発の文脈に対して距離を置いていることを明言しておく。あとこういうテーマでとある原稿書いていたので、頭の整理も兼ねて。

ActiveRecord の功罪を振り返る

このテーマを語るにあたって、まず RailsMVC について述べなければならない。なぜなら、フロントエンドのアーキテクチャとは、サーバーサイドの MVC の模倣に始まり、破綻し、結果として iOS/Android/Desktop の GUI アプリ設計手法と合流したからだ。

Rails の ActvieRecord の失敗は、端的に指摘できて、それは Storage 層 と、ドメインを記述するための Entity を区別しないというところにある。本来の MVC を WAF に特化した省略形で、この設計は MVC2 と言われることもある。

MVC と MVC2 について改めて考えてみる - スタジオ・アルカナ技術ブログ

この MVC2 は Web 特有のリクエスト・レスポンスに起因していて、コントローラで受けてから値を返すまでのライフサイクルが以上に短い(50ms~1500ms)。また雛形のコードも短い。なので、Enitity と Storage を区別しているステップがもったいない。

あえて区別しない express の擬似コードを書くならこうだろうか。

server.post('/users/save', (req, res) => {
  const attrs = req.body
  // 無意味なコードなのであえて冗長に書いている
  const newUserEnitity = new UserEnitity(attrs)
  const userStorage = await UserStaroge.save({
    id: newUserEnitity.id,
    name: newUserEnitity.name,
    email: newUserEnitity.email
  })

  res.json(userStorage.attributes)
})

まず req.body が外部 IO から来る値なので静的検査しづらい。最終的に値を返す res.json(...) も同様。*1

そして外部 IO から来る値を元に Entity を組み上げても、短いコードではそのロジックを使う間もなく、Storage にセーブして終わり、となりがち。なので、この Entity と Storage は一体化した Model という名前のストレージ兼ロジック抽象の何かになった。そしてたぶん CakePHP で導入されたその思想は Rails 等の他の MVC に受け継がれていった、と理解している。

簡単なうちは簡単で済む。それはいいことだと思う。他を害さない限り。

追記: ActiveRecord パターン、 Cake => Rails という順番だと聞きかじっていたけど、Martin Fowler の PoEAA => Rails => Cake3 らしいです

良い解決策とは何か

セットアップを短くして、初心者や初学者にいい顔するのはいいことで、不必要なことを抽象化できてるということだし、興味のスコープを宣言できてることなので、悪いことではない。

最近でも next.js なんかはそれの権化で、pages/index.jsexport default () => <h1>Hello</h1> と書くだけで SSR する React アプリケーションの開発がはじめられる。この体験は鮮烈だった。確かに、本質的に削ぎ落とすと最初はこれだけでいいはずだ。

https://github.com/zeit/next.js/

で、問題は、これが複雑化した画面でどういう柔軟性があるか。

next.js の難点は、多様な要求に対してオプションを提供するのではなく、 作者の @rauchgミニマリズムな思想に従うことを強制してくるタイプのフレームワークで、その点使い勝手は後発の nuxt.js に劣る。

Rails に Service 層を生やすかどうかよく議論にあがるが、それは ActiveRecord が Entity としての振る舞いをどこに書くかの居場所が RailsMVC モデルだと用意されてないからだと思う。Controller が分厚くなったら、共通処理は Model に書きましょう、というのがこのアーキテクチャから自然と導かれてしまうアンチパターンで、これは悪い DRY の話にもつながる。

俺が悪かった。素直に間違いを認めるから、もうサービスクラスとか作るのは止めてくれ - Qiita

クライアントの「モデル」

前置きが長くなった。で、本題のクライアントサイドのモデルだが、複数の解釈が発生してしまった。

  1. サーバーサイドのモデルを抽象したプロキシ
  2. クライアント上のユースケースを表現した Entity
  3. クライアント側の永続層(IndexedDB/LocalStorage) のストレージ抽象

これに対して誰も一貫した答えを持っていなかった。というのが 2012 年ぐらいから段階的に明らかになったことで、DDD 的解釈や素朴な WAF の延長と捉えた人で解釈が違っていた。僕は Electron アプリの開発をしていたので 3 とも向き合うことになった。

1 が Backbone と Ember で、これは前提に REST がある。1 画面が 1 つの Model に紐付いていて、その Model のクライアントにおける写像を用意すれば、一貫した開発体験が得られる、というわけだ。

結果から言うとこれは破綻した。理由は 2 つある。

まずサーバーとクライアントのライフサイクルが違う。サーバーは先に述べたようにせいぜい 200ms~15 秒だが、クライアントサイドはタブが生成されてから破棄されるまで動き続ける。(ここは暗に SPA を意図している) なので、想定すべきは 3 分とか 15 分、しかも複数レスポンスに跨って状態を持つ、みたいな話になる。そのトリガーは何かしらのイベント駆動で、また抽象が違う。そもそもリソースに関与しない振る舞いすらある。

2 つ目は、クライアントサイドで発生するリレーションの問題で、クライアントの要求が増える度、必要なデータのクライアントサイドのジョイン が発生する。開発が長い環境ほどインターナルな REST 抽象は破壊され、専用 ViewAPI が増えるかカスタマイズされるかどっちかになる。ちなみに、これに対する解答の一つが GraphQL だったりする。 (ちなみに自分は REST 懐疑派で、もはや誰から見てもユニバーサルなリソースなど存在せず、クライアントからの要求は専用 API か RPC を作るのが良いと思っている)。

node だと isomorphic というテーマがあって、それを無理矢理に一致させようという研究は行われていたが、結果として上手くいったとは言えない。専用の PaaS が必要な Meteor は結局流行らなかったし、結果としてその差を強く意識するようになってしまった。

じゃあどうなったのか?それは ウェブの MVC という前提を捨てて、Flux という名前で MVC モデルを見つめ直したことで、結果として GUI プログラミングと潮流と合流しつつ、ストリームの監視と差分適用というものにフォーカスしたパラダイムに進化したのだ、と自分は思っている。

後編に続く。

mizchi.hatenablog.com


おまけ: 初心者にいい顔できるツールが流行る

やや愚痴っぽい話。

ちょっと本題からずれるが、プログラミング言語フレームワークの流行は、以下に初学者に対していい顔をするか、という点に尽きると思っている。かっこいいものを手数少なく書けるとカッコイイ。チュートリアルは短ければ短いほどいい。ここ近年の静的型付の復権は、単に型表現のパターンや推論機が発達して、チュートリアルのサンプルコードを短く書けるようになったかどうかに過ぎない点もあるのではないか。

これは悩ましい問題で、実際仕事でアプリケーションを書いていくにあたって、フレームワークの選定などを行うアーキテクトの立場では、コミュニティで人気があるものと、アーキテクチャ的な伸びしろがあるかどうかはまったく独立した要素だ。前述した next.js は、僕も便利だと思いつつ、仕事のような要件がコントロールできない場合に採用するのを薦めることができない。SEO 上の理由で SSR する必要があるなら苦労してでも redux SSRのボイラープレートを一つ採用するのを薦める。そもそも SSR 不要なら SSR 不要であると言うことのほうが多い。

極端なのは「プログラミング抜きで〜できる!」という煽りで、その場合、プログラミング言語に等しい一つの DSL やツールを覚えることになるのだが、それらのツールがその説明を果たしているとは言い難い。極端なのは RPG ツクールだと思っていて、例えば僕がプログラミングを最初にやったのは、WolfRPG Editor(ウディタ) の戦闘画面のコモンスクリプトを改造していて、こんなんプログラミングじゃん!って思ってはじめたのが最初だったような気がする…。

*1:個人的に、ここの静的検査の弱さが 10 年前の動的型付ブームの理由の一つだったのでは、とも思っている。自分は逆にアノテーションとしての型を書くべき派だが…