クライアントサイドのモデルとは何か 前編 ~ クライアントサイド MVC の死
前置き
この記事、本来は Flux には Model がないのではないかと思った覚書 - ナカザンドットネット と Flux の Store が ViewModel かって話からの MVW とかどうでもいいって話 - 猫型の蓄音機は 1 分間に 45 回にゃあと鳴く のアンサーとして書き始めた記事だが、前置きだけで別テーマとなったので、前後編に分割する。
僕は元々がゲームクライアント屋だったときの発想を引きずってるのと、既存の Web の開発の文脈に対して距離を置いていることを明言しておく。あとこういうテーマでとある原稿書いていたので、頭の整理も兼ねて。
ActiveRecord の功罪を振り返る
このテーマを語るにあたって、まず Rails の MVC について述べなければならない。なぜなら、フロントエンドのアーキテクチャとは、サーバーサイドの 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.js
に export default () => <h1>Hello</h1>
と書くだけで SSR する React アプリケーションの開発がはじめられる。この体験は鮮烈だった。確かに、本質的に削ぎ落とすと最初はこれだけでいいはずだ。
https://github.com/zeit/next.js/
で、問題は、これが複雑化した画面でどういう柔軟性があるか。
next.js の難点は、多様な要求に対してオプションを提供するのではなく、 作者の @rauchg
のミニマリズムな思想に従うことを強制してくるタイプのフレームワークで、その点使い勝手は後発の nuxt.js に劣る。
Rails に Service 層を生やすかどうかよく議論にあがるが、それは ActiveRecord が Entity としての振る舞いをどこに書くかの居場所が Rails の MVC モデルだと用意されてないからだと思う。Controller が分厚くなったら、共通処理は Model に書きましょう、というのがこのアーキテクチャから自然と導かれてしまうアンチパターンで、これは悪い DRY の話にもつながる。
俺が悪かった。素直に間違いを認めるから、もうサービスクラスとか作るのは止めてくれ - Qiita
クライアントの「モデル」
前置きが長くなった。で、本題のクライアントサイドのモデルだが、複数の解釈が発生してしまった。
- サーバーサイドのモデルを抽象したプロキシ
- クライアント上のユースケースを表現した Entity
- クライアント側の永続層(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 プログラミングと潮流と合流しつつ、ストリームの監視と差分適用というものにフォーカスしたパラダイムに進化したのだ、と自分は思っている。
後編に続く。
おまけ: 初心者にいい顔できるツールが流行る
やや愚痴っぽい話。
ちょっと本題からずれるが、プログラミング言語やフレームワークの流行は、以下に初学者に対していい顔をするか、という点に尽きると思っている。かっこいいものを手数少なく書けるとカッコイイ。チュートリアルは短ければ短いほどいい。ここ近年の静的型付の復権は、単に型表現のパターンや推論機が発達して、チュートリアルのサンプルコードを短く書けるようになったかどうかに過ぎない点もあるのではないか。
これは悩ましい問題で、実際仕事でアプリケーションを書いていくにあたって、フレームワークの選定などを行うアーキテクトの立場では、コミュニティで人気があるものと、アーキテクチャ的な伸びしろがあるかどうかはまったく独立した要素だ。前述した next.js は、僕も便利だと思いつつ、仕事のような要件がコントロールできない場合に採用するのを薦めることができない。SEO 上の理由で SSR する必要があるなら苦労してでも redux SSRのボイラープレートを一つ採用するのを薦める。そもそも SSR 不要なら SSR 不要であると言うことのほうが多い。
極端なのは「プログラミング抜きで〜できる!」という煽りで、その場合、プログラミング言語に等しい一つの DSL やツールを覚えることになるのだが、それらのツールがその説明を果たしているとは言い難い。極端なのは RPG ツクールだと思っていて、例えば僕がプログラミングを最初にやったのは、WolfRPG Editor(ウディタ) の戦闘画面のコモンスクリプトを改造していて、こんなんプログラミングじゃん!って思ってはじめたのが最初だったような気がする…。
すべてのプログラマが機械学習を受け入れる準備をする時代になった
という予感がしたので書く。正確に言うと機械学習の成果としての訓練モデルを。
まず事前に前置きしておくと、僕は機械学習をほとんど抑えていない。トレンドだけ追ってる。
大学生の時にニューラルネットワークを実装してみてフ~ンって言ってた程度に知識しかなくて、ディープラーニングが流行る前だから、「バックプロパゲーションってややこしかったけど、今は自動でモデルの最適化いい感じにやってくれるんでしょ?」ぐらいの雑な理解しかない。(この時点で怪しい)
で、今はフロントエンドやってて、ここは機械学習は縁遠いように思えるかもしれないだろうけど、最近のGoogleはなんとブラウザで tensorflow を動かすのに情熱を注いでいる。
で、こんなのが Hacker News で流れてきた。
とりあえず試した。デモをそのままデプロイした。
で、こんな感じに動く
大学生の時にKinectを使った心理学の実験をしていたので、7年ぐらい前のKinectの精度は知っている。それと同等ってほどではないが、特殊なセンサー、複雑なセットアップ、キャリブレーションなしでこの精度が出るのはすごい。しかも時系列解析ではないからヒントも少ないのに…
要はディープラーニングやその辺でどんどん進化してるってことだろう。たとえばこれで簡易vtuber作れると思う。一週間あれば配信ツール作れそう。
機械学習を受け入れる準備
要は複雑なモデルの訓練はデータ集めて下処理してGPUクラスター組んだりパイプラインを組んだりして非常に大変だが、それを訓練したモデルのサイズはそこまででもないので、それをクライアントに配って使えるようになった。
一般化したこそ、モデルを使う側も準備を整えないといけない。posenet は、これからのエンジニアは機械学習で生成されたモデルの受け入れ体制が教養になるな、という予感を感じさせるプロダクトだった。
他にもこういう話が出てきた。
[速報]AIがコードのレコメンドやバグの指摘など開発を支援してくれる「Visual Studio IntelliCode」発表。Build 2018 - Publickey
補間エンジンをAIで賢く出来るのでは、というのは僕もアイデアとしてはあったし、たぶん頑張れば作れるだろうという予感はあったが、これはMSだけどこれから先はテクノロジージャイアントじゃなくてもモデルをどんどん作る時代になるだろうし、Kaggle の流行なんかもその予感を感じさせる。
要は機械学習の利用者として賢く振る舞わないといけない。フロントエンドエンジニアとしてはとりあえず tfjs 使えるようになっとけという話で、サーバーだったらもっと大規模なデータを訓練しながら動かせるだろう。
機械学習の受託は納品物が曖昧、みたいなのがよく話題になるけど、これからはより身近になって開発環境構築とかそのレベルで身近になるのだろう。
30歳になった
10年前に上京して、Twitterをはじめて、昔はどの勉強会やオフ会いっても最年少かそれに近い感じだったが、今となってはそんなこともなくなった。
働き始めた後、25歳頃に自己分析した結果、「好きなことしかできない」「勝つのが好き」というあまり社会人適正が高くない特徴を自覚した。なので、とりあえず現状自分が勝ててる分野をそのまま突き詰めることにした。フロントエンドとSPA。その戦略は間違ってなかったが、コンフォートゾーンに甘んじている感覚もある。俺の評価が上がったんじゃなくて、時流にいい感じに乗れてたという感もある。
今はフリーランスとしてやっていけてる。幸い仕事は十分ある。でもなんか妙にこじんまりとした感じに収まっている。なんというか、冒険していない。20代はベンチャー企業で働いてあわよくば一発と思っていた。それも途中で諦めてしまって、今はフリーランスでコンサルとエンジニアの真ん中みたいなことをしている。
最近、大きな勉強会にいくと、「ファンです、ブログやPodCast聞いてます」みたいな声を掛けられることが多くなった。俺ごときが、みたいな気持ちもありつつ、恥ずかしがりつつ照れながら適当にあしらっててしまい、なんか上から目線の俺きめえな、みたいな気持ちになり、帰ってから反省したりしている。
人生にマンネリを感じる。ちゃんと人生やっていってる人はこのへんで子供が生まれてライフスタイルが変わっていくんだろう。自分がそういうことをできるか?と自問すると、結構厳しい気持ちになる。自分が誰かと一緒に暮らしている姿がイメージできない。
じゃあ自分のために時間を使えているかというと、それも微妙で、アニメや漫画で満足できることが減った。単に要求水準が高くなってるのはわかっていて、馬鹿になって愉しめば良いのだが、それができない。最近はネットフリックスのドラマを見ることが増えたが、やっぱ英語でネイティブに楽しみたいよなーと思って、英語を真面目にやるモチベーションだけは上がっている。最近ヒアリング力は多少上がった気がする。ゲーム・オブ・スローンズのイッキ見とかしたからだと思う。原作追い越しててびっくりした。
肩が痛い。肩と首が痛い。エンジニアの皆、姿勢は大事だ。高い椅子を買え。筋トレをしろ。痛みに集中力がもっていかれて、強制的に休憩が必要になる。整体にかなりの金を掛けてる。それでもよくならない。
最近何かやってるかといえば、仕事でまとまった分量のコードを書くことが減ったので、プログラミングの基礎体力のためにと思ってゲーム作ってる。この時期毎年作ってる気がする。今年はアクションゲーム、というか Path of Exile をやっていたらDiabloクローンを作りたくなった。
http://zen-brown-07f2ef.netlify.com/
中途半端にOSSとか考えると妙に色気を出した縛りで積むことが多かったので、今回はOSSにしない縛りでひたすらゴリゴリ書いてる。基礎部分は出来てきたが、まだゲーム性みたいなものが発生していない。ゲームというよりゲームエンジンを作ってるという感じがある。
30歳だし何か書くか、と思い立って見たが、特に新規性ある内容はなかった。
とりあえず例のリストです。
https://www.amazon.co.jp/registry/wishlist/3MSOG970O7XT9/ref=cm_sw_r_cp_ep_ws_2lD6AbXP2P13K
https://store.steampowered.com/wishlist/profiles/76561198033602710/#sort=dateadded
シャニマスのコード読んだ
ライセンス表記みるぐらいで深掘りはしてない
シャニマスのコード読むか
— human eslint --fix (@mizchi) 2018年4月24日
シャニマス、pixijs ですね
— human eslint --fix (@mizchi) 2018年4月24日
pixi-ae.min.js たぶん after affect のコンバータなんだろうけどググっても引っかかんないな、内製かな
— human eslint --fix (@mizchi) 2018年4月24日
s/after-affect/after-effect
After effects to pixi.js | CodeSeek なら見つかったが、該当のライブラリ名で探してるが見つからず
パーティクルはこれ https://t.co/NtIqPBBq7n
— human eslint --fix (@mizchi) 2018年4月24日
pixi-particles これっぽい https://t.co/OQxwgC90Cq
— human eslint --fix (@mizchi) 2018年4月24日
pixi-filters, elliptic, underscore, polyglot, md5
— human eslint --fix (@mizchi) 2018年4月24日
シャニマスちょっと古い React 使ってるからパテント条項付きBSDだ。Facebookからの差し止め命令があったら拒否できない(そんなことはないだろうが)
— human eslint --fix (@mizchi) 2018年4月24日
正確にはFacebookのライブラリにちょっと前まで共通でついてたBSD+Patentの条項のライセンス表記を発見したが、componentDidMount で grep して引っかからなかったので、 Reactではなく別のライブラリのような気がする。ImmutableJSかなにか。
aoba.loaders.Resource ってやつ自作のアセットローダーかな。アイマスにそんなキャラいそう(詳しくない)
— human eslint --fix (@mizchi) 2018年4月24日
青羽美咲とは (アオバミサキとは) [単語記事] - ニコニコ大百科
aoba がシャニマス内部の自作フレームワークっぽい。グローバル変数に露出してる
— human eslint --fix (@mizchi) 2018年4月24日
たぶんだが TypeScriptの namespace を感じる
— human eslint --fix (@mizchi) 2018年4月24日
aoba.GC_MODES = { AUTO: 0, MANUAL: 1 } とか
— human eslint --fix (@mizchi) 2018年4月24日
生えてるメソッド的に pixi.js の ラッパーか
— human eslint --fix (@mizchi) 2018年4月24日
でも aoba.Button とか汎用的なUIも生えてて統一感のなさもなんか内製フレームワークっぽい
— human eslint --fix (@mizchi) 2018年4月24日
あ、これ アニメーションはSpineだわ
— human eslint --fix (@mizchi) 2018年4月24日
追記
普通に難読化されてないやつ読まなくてもライセンス表記あった…
シャニマス、 これ見ただけで mining してる!とか騒ぐつもりはないが bitcoin-js が入ってるのなんでや
— human eslint --fix (@mizchi) 2018年4月24日
それ、jsrsasignの一部じゃない? https://t.co/T2MLLpUn5v
— サブカルペンネくん (@pastak) 2018年4月24日
たぶんだけどアセットの暗号化で使ってるモジュールの一部に blockchain の何かの何かがあるみたいな感じかな
他目ぼしいのは page.js (routing) と emscripten だった。emscripten あるならこれ以上追跡しても無理くさいが
— human eslint --fix (@mizchi) 2018年4月24日
感情の共有の時代だからこその Podcast 〜 今日からはじめるPodCast #技術書展4
今週日曜日(4/22)の技術書展の、PodCast 本の宣伝を任された mizchi です。最近推してるPodCast は https://turingcomplete.fm/ です。
この同人誌。
表紙を見ての通り、非常に力が入っています。 yatteiki project の1企画みたいな位置づけの同人誌です。
discord で PodCast 収録する方法っていう妙にピンポイントなソリューションが紹介されてて、SoundFlower, Ladio Cast, Audio Hijackなどを使う方法の紹介されています。こういうのに興味がある技術系PodCastの人は大抵Macなんでだいじょうぶでしょう。nodeでdiscord API叩いてる感じなんで Win の人もまあなんとか。
いつでも撮れるようにしておくってのは本当に大事で、僕も genba.fm っていう PodCast やってるんですが、何回かやって録音に関するソリューションが複雑化してしまって、忙しさとあいまって更新が止まってしまったんで、この本にある discord での podcast を動かして再構築してる最中です。またすぐやりたいですね。
9m の技術的な内容の他には itopoid の番組の企画方法の話がエモい感じです。
itopoid の PodCast本を書評書くのに読んでるけど、適性診断、いきなりハードル高くねえか https://t.co/Pid1mejbA8 #技術書展
— human eslint --fix (@mizchi) 2018年4月20日
PodCast に何を期待するのか
で、ここでポエムをはさみたいんですけども。
まず最初に言っておきたいことは、箇条書きになった技術スタックだとかノウハウ集には、テキストの方がいいわけですよ。コピペできて、検索できて、各自の速度で読めて、便利。
ポッドキャストへはそういう属性のリンク集的なものは期待していません。じゃあ何を?
僕が思うに、漏れ出る美意識とか、対象に対する温度感とか、その当人の譲れないものとか、または話の展開の傾向、それによって滲んでくる人格、そういうものが PodCast に期待されるものだと、僕は理解しています。
だからこそ、偶発性は大事で、事前の台本通りにはいかない上でオチがつくのがいい番組だと思っています。ただし事前の併せ読みはします。最初に、告知があって、「わ〜」とか言ってるのは、(僕とitopoidのやるやつは)結構な確率で茶番です。聞いてる方も、なんとなくわかるでしょうが…
僕はあんまりにも台本的なセリフを読んでると、耐えられなくて吹き出しちゃったりするんですが、itopoid は結構そのまま通していて、これが「エンタメ的な所作か…」と感心してたりしますね…
ちなみに、 yatteiki.tv の方は @sageszk というニコ生などで生配信の機材セットアップやその運用などを本職でやってる友人に手伝ってもらっていて、併せ読み用のプロ品質の原稿があって、事前打ち合わせをみっちりやって、話す話題、話せない話題を調子して、複数台マイクがセットアップされてミキサーで調整されてたり、あれは手間が掛かってます。Youtuberはたいへん。vTuber とかもっと大変だから、興味ある人はとりあえず PodCast など取ってみたりするのはいかがでしょうか。美少女にはなれませんが…
mizchi の PodCastへの出演
[フリーランスの現場のmizchiのやっていき 前編 | yatteiki.fm](https://yatteiki.fm/episode/44)
[Rebuild: Aftershow 66: Hey Siri, Deploy Production (r7kamura, mizchi)](https://rebuild.fm/66a/)
[ajitofm 21: the State of Front-End](https://ajito.fm/21/)
書き出してみたら結構出ていた。暇だったら聞いてください。
誰もが話すべきテーマを抱えているはずです。インターネットでは誰もが主役になれる。そういう、ちょっと古いかもしれないインターネットのイデオロギーで動いてるのが yatteiki です。
キャッシュフレンドリーなステートレスアプリケーション設計について考える #CDN_Study
CDN_Study という勉強にいってきた。 https://http2study.connpass.com/event/81469/
そこで、Akamaiの方が、「個人の意見だけど、アプリケーション側がもっと基礎設計でステートレスでキャッシュフレンドリーな設計になってないといけないよね」という旨の発言をしていて、最近そのことにアプリケーションエンジニアとして同じようなことを考えていたので、書き出してみる。
SPAとかSSRとかフロントの不毛な話は出さないようにしてるが、主にサーバレス環境を意識している。
前提
- 世の中のアプリケーション内のモジュールは、Statefull or Stateless に分類でき、それをツリー状に表現できれば差分検知できる、という React の仮想 DOM 的な世界観が自分にある
- 以下の話は、基本的には Fastly のサロゲートペアーとそのためのミドルウェアを作れば既に実現できる話ではある
- (Akamaiは自分は使ったことないです。ごめんなさい…)
参考: https://docs.fastly.com/ja/guides/purging/single-purges
ウェブアプリケーションへの需要/要求
現状のウェブアプリの設計において、動的ページの HTML をキャッシュさせるのは、それ専用の設計をしないととても難しい。まず、何が入力に対してべき等で、何が外部IOを含んでいるかを事前に分類する必要がある。
で、それを乗り越えると何が嬉しいか。PWAやAMPみたいなフロントエンドからみたパフォーマンスチューニングでは、ある一定以上の体験を求めると、キャッシュ済みの *.html
を CDN のエッジに配信させる必要がある。基本的にアプリケーションサーバに飛ばしたら負け。ここで同意が取れない人もいると思うが、そういう前提とする。
HTMLをエッジに配信させると、体感値としては、1000~5000ms ぐらいのオーダーだったものが50~150ms のオーダーの世界になる。このインパクトは大きい。しかし現状そのアーキテクチャを取るための手法は発達していないので、誰でも出来るといった状況にはない。
具体的にあったらいいなと思うもの
fastly にはサロゲートペアーという機能がある。これはパスに対してタグを打っておいて、そのタグがついてるものをまとめてパージするというもの。
これを使って、たとえば /article/1
というURLがあって、HTMLレスポンスが変える時、そのページのキャッシュ判定を行うサロゲートペアーは次のようなオブジェクトで表現できるかもしれない。
var Relations = { '/': ['APP_VERSION:0.0.1', 'app.js', 'app.css'], '/article/1': [ 'APP_VERSION:0.0.1', 'app.js', 'app.css', 'function:getArticle?id=1:update_at=1523717246877' ], // ... }
まず、デプロイの度に全破棄するような実装のために、 APP_VERSION みたいなキーを張っておく。 他は、ある他のリソースに対して、サロゲートキーへのリレーションの表現をする。
app.js, app.css に違和感はないだろうが、3番目のサロゲートキーで update_at をキーにしたものを登録することで、なんらかの function(AWS Lambdaなどを想定) へ渡すまえにキャッシュ判定をして、CDN内で外部IOが発生することなく高速にキャッシュを返せる判定ができるはず。
これをどのように実装したいか。たとえばこれがDBアクセスを仮定すると、基本的には何らかのDBのレコードのtimestamp を収集することになると思う。
これを実現するような、Node.JS の擬似コードを書くならこう。
// なんらかのORMの post-update を想定 Article.onUpdate(item => { AssetCache.invalidate('function:getArticle?id=1:*') AssetCache.registerNewSalogateKey( 'function:getArticle?id=1:' + item.update_at ) })
あえて冗長な表現をしている。
で、事前に宣言した Relations に対して、 invalidate したアセットに関連するものを再帰的にパージすれば、「id=1 で表現した入力とその結果が本当にべき等なら」、これで更新後に最初の一回だけ function を実行してあとはキャッシュを返し続けるような実装が、たぶんうまくいく。新規デプロイ時はAPP_VERSION更新すれば全部消える。function を個別に管理するならAPP_VERSIONも不要。運用難しいけど。
ここがうまくいくと嬉しいのは、キャッシュルールはデプロイごとに静的として、サーバーは単方向に invalidate を発行し続けるだけ、クライアントのリクエストも常に同一、動的なのはキャッシュミドルウェアのKVS的な内部状態だけで、デプロイまたはDBのレコード更新に動的に反応するだけのステートレスなアプリケーションが出来る。
こんな実装は可能なのか
構成としてはこうなるだろう。
Function(Application) <=> CasheMiddleware <=> CDN <=> Browser
上に述べた実装を仮に CasheMiddleware という名前にしたが、こいつで上のサロゲートキーのリライトルールを実装する。内部ステートは大雑把に redis かなんかで作れる。転地インデックス作るぐらいでそんなに難しくない。
Fastly のキャッシュパージ速度は公称150msなので(正直実測はしてない)、一時的な不整合も最小限に抑えられる。CloudFront は10分ぐらいかかるので、そうもいかなかったりするが。
それで、実際にはアプリケーションコードから、関連サロゲートキーをフレームワークの機能として、動的に決定できるようにする。たとえば、アセットは自明として、なんらかのビューライブラリで
<x-article-component data-json={ useCache(fetchArticle(1)) }/>
みたいなヘルパをかまして、中のエンドポイントを収集してヘッダに埋め込むなりして紐付けたうえでViewとそのリソースへのリレーションを生成すればいい。
この実装を作る気があるか
NO
なぜならこの設計はfastlyのみで動くサロゲートキーの仕組みに強く依存している。fastly でしか動かないものを作りたくない。(個人的な話で言えばフリーランスなんで fastly が使えないと最適化できませんみたいな人材になってしまうのが一番のリスク)
また、セッションを考慮してないので、クッキー依存の動的ページをJSまたはwebcpomonts/iframeなどに必ず切り離さないといけない。そうしないとちょっと前のメルカリで他人のクレカが見えちゃった的なミスが起こる。JS書く人間には抵抗はないが、これを受け入れられない人も多いだろう。だいたいHeaderの右側のログイン情報とか。
また、全てのAPI実装が関連するレコードのタイムスタンプに対してステートレスである、という制約が厳しく、運用で事故る未来しか見えない。ここは人間を信用してはいけないレイヤー…。
しかし、ApplicationServerでHTMLを作って返すより、CDNエッジからHTMLを返した際に得られる速度はわかりやすく圧倒的なので、「fastly のサロゲートペアーまたはそれに準ずる仕様が何らかの標準にあるなら」、「フレームワーク組み込みでサロゲートキー生成を込みで」実装する恩恵が出てくる。ここまできてはじめてフレームワークかなんらかのヘルパ作って嬉しくなるなぁ、という感じ。そうなったら 特定のCDN 使わなくても nginx middleware とか書けばいいし。
ただ、「こういうカスタマイズ要素を標準化しちゃうと、各プラットフォームの売りがなくなっちゃうよね」みたいな話があるのもわかる…ビジネス難しい…
個人的には、CDNはキャッシュのパージ速度、IOへの課金、ストレージサイズへの課金で選ぶので、DevelopperExperience 以外の内部拡張がどうこうって話は正直知りたくすらないみたいな気持ちもある。あるいは世の中すべてが fastly になるならサロゲートキーにロックインされに行くぞ!ってなりますね。
終わりに
なんかこういうの既にありそうだけど面倒で調べなかった。 キャッシュファーストなアプリ設計、初心者がハマりがちで、あんまり流行らなさそうという実感もある。 パフォーマンスチューニングおじさんしてると、諦めてスクラッチで書き始める。辛い。
プロトタイピング
Path of Exile ってハクスラにはまってるんだけど、ゲームってやってたら作りたくなりませんか。 途中まで作った。
https://infallible-easley-5955b5.netlify.com/
使ったもの
- React
- Redux
- SVG
ひたすら素朴な react-redux で作るのを心がけた 他はすべて気合で0から実装
実装したもの
- 経路探索
- カメラ
- ポインタシステム
- 当たり判定
詰んだ
きれいな設計にするために副作用起こせる場所とアクセス絞ってたら無限に想定から外れて、「絶対に副作用を起こさないと詰む」みたいな箇所が増えてしまい、コードを大幅に書き直さないといけなくなったので、一旦断念。オブジェクト間のメッセージシステムをもっと念頭に置くべきだった。
React部分で困ったことはない(複雑なものもない)。当初懸念していた reducer の実行コストと描画コストだが、pure つけまくるとどうとでもなった。
とはいえSVGをマップ描画すると、描画コストが想定よりだいぶ重かったので、諦めて Canvas で描画して data URI をimage要素にぶち込むと速かった。