Posted at
CureAppDay 25

クライアント主権時代にJSのモデルはどう共有すればいいのか

More than 1 year has passed since last update.


クライアント主権時代

SPA、スマートフォンアプリの隆盛。時代はクライアントサイドに主権が移ってきている。

これからの時代はクライアントサイドにロジックを持たねばならぬだろう。

一方サーバーは、「ユーザー1人でも完結するサービス」に限れば、

DOA(Data Oriented Architecture)的な立ち位置のもの(≒mBaaSとか)と、BFF(Backend for Frontend)的な立ち位置のものがあればよくなる。


モデル定義がダブる

いままでモデルはサーバーのものだった。

クライアントでは「JSON」というデータになってやってきて、

それをなんとなく成形して表示してた。

でも来るべきクライアント主権時代においては、

やっぱりクライアントでもJSONじゃなくてドメインモデルを扱いたい。

そうするとサーバー/クライアントの2つの環境でモデル定義を書かなくてはならないではないか。

多くのサービスは実は同様な問題に直面していて、


  • ちょっとしたロジックは2重で持つ

  • サーバーに全部計算させて通信を介して渡す

というworkaroundになっていた。


Protocol Buffersなのか?

GoogleのProtocol Buffersは、

プログラミング言語によらないモデル定義の方法を定めている。

標準化するとすればこの方向になるかもしれない...しかし、


  1. nullableでない値しかとれない(0とnullは区別できない)

  2. JS実装が柔軟でない

  3. メソッド的なものの定義はない

という弱点があった。(これはあくまで主観ね!)

それに、やっぱり3が大事。

ロジックの共有をプログラミング言語をまたぐにはLLVM的なしくみが必要になったりする。


Universal JavaScriptでモデル共有

だからプログラミング言語を統一すると利がある。

そこで、サーバーでもブラウザでもiOSでもAndroidでも動くし実績もあるのはJavaScriptだ。

言語仕様が気に食わないのは百も承知でも、圧倒的な人気と実績とサポート環境を持つ彼を

我々は無視できない。

これがUniversal JavaScriptで成し遂げたいこと。

ちなみに私もSSR不要論には大いに賛同している。

そもそも、UniversalJS = SSR というのは違うと私はかつても訴えているよ。


すでに解決されたいくつかの課題

UniversalJSでできることはあまり多くないと思われているようだが、

実際には色々出来る。

マルチプラットフォーム時代のUniversalJSに詳しく書いたけれど、

インフラ層へのアクセスをHTTPにして、それをfetchのようなAPIで統一するだけで、

多くのことが解決する。


データの受け渡しに課題

で、サーバーとクライアントでモデル定義を共有したとしても、データの受け渡しはJSONだ。

それをモデルにする部分、モデルからJSONにする部分ってのがどうしても必要になる。

ここはProtocol Buffersのような仕組みで、

環境をまたいだモデルの復元まで可能になるような世界にするのもいいのだが、

新しい仕様を欲する人は少ないかもしれない。


serialize/deserializeの仕組みをどう解決するか

このJSON <=> モデル 変換 の課題はいかに解決すべきなのか。

当然、Universalなモデルフレームワークを作るという考えがある。

僕はそのアプローチを取って、OSSも作って、ある程度うまくいっているつもりだが、

フレームワーク習得コストやロックインの問題がある。

すべてのモデルが何らかの子クラスであるような状態ってどうなんだろう、という。

easyよりsimpleのほうが好まれる文化には逆行する。

easyとsimpleについてはこれ


  • easy: railsのようにhas_manyとか書けばよしなにやってくれる

  • simple: 依存がなく、コードを読めばすべてが書かれている

(immutable.jsもeasy寄りで、同様の課題を持っていると思う。)

となると、着脱可能なモデルのserialize/deserializeの仕組みがあればいいなぁと

思う次第である。


high order function

継承しないでこういった仕組みを注入するのは、JSだとわけなくできる。

何の躊躇もなくhigh order componentを使っているreact勢なら、

mixin的な関数をくぐらせればいいじゃんとか思ってくれるかもしれない。

ただmodelはsimpleに!という思想からすると、クラスに関数をくぐらせるのは

違うかな、と。(これはあくまで主観ね!)


そもそもクラスにしないで第一引数にplainいれる関数でいく

モデルはclassにしないで、第一引数にplain objectを入れてそれを操るもの。

functional DDDとか、「状態と振る舞いは分離しろ!」というようなたぐいのもの。

Objective-Cの世界と似ているもの。

チーム全員がこの思想に共感すればそれでもいいかもしれないが、僕はやっぱり

User.getAge(plainUser)じゃなくてuser.ageがしたいですね。


decorator

将来的にできるかもしれない文法に賭けるというのも微妙だけど、

着脱可能なのはいい。


setPrototypeOf

JSONをもらってモデルを作るときのconstructorが面倒なのであれば、

Object.setPrototypeOf()というメソッドを利用すれば、plain objectが一瞬にして

クラスのインスタンスになってくれる。

どうかんがえてもこれは奥の手的で万人受けしないだろうし、

モデルがモデルを持っているような階層的な場合に対応できないからあまり筋はよくないかも。


babylonにflowの型を読ませてtranspile

transpile時にコンストラクタ自動生成という怠け者の発想。

DRYの精神は褒められたものだが全然コードが追えなくなる。


手書き

constructorを1回書いてしまえば済むこと。

しかし人間はミスをする動物なので。


コマンドでコード生成

やっぱりconstructorを手書きするのは辛いこともあるので、

constructorを書いてくれるコマンドがあればいいのだ!

そこでbabylonにflowの型を読ませればいいってことか!

ただ、flowは非標準なのでそれに依存するっていうことにもなる。


やり方はチーム次第

今のところベストプラクティスが存在しないので、

チームで上記パターンのどれがいいのか、他のよい方法はないのか、話し合って

決めるといいのではないのかと思っている。

プログラミングは決して数学のように答えが決まっているものではなくて、

人間が使いやすいためのものなのだから。