技術と魚

技術調査、開発Tips、及びしょうもない文章

SPAの開発におけるInstruction Passingという新たな構想

近年SPAというのが流行っています。Webがリッチ化する必要性が出てきたという点や、JavaScript周りの技術が非常に熱を帯びている点が理由と考えています。今はReactやVueなどのフロント向けFWも成熟期です。

しかしどうにもしっくり来ていないのが、その間を支える通信です。

「特定のURLにブラウザでアクセスしたらHTMLを返せばいい」という時代、サーバの直接的な利用者はユーザ自身でした。しかしSPAでは、クライアントアプリが直接の利用者になっています。*1このような動きは自然と、サーバに対し"クライアントのために"機能を作ることを要求するようになりました。この2つの時代をそれぞれ、第一世代第二世代と呼ぶことにします。

第一世代では、MVCのアーキテクチャが採用され、8割ぐらいの要求に対しては典型的な方法で解決策を見いだせる状況でした。せいぜい2割程度のケースでのみ、例外的なコードを書けば済むのであれば、MVCは十分に効率的と言えました。

第二世代において、サーバが渡すべきデータは、より限定的なものでよくなると考えられました。特に、一般のREST APIの様に、要求されたリソースを返すエンドポイントを適当に作っておけば、「あとはクライアントサイドがなんとかしてくれるだろう」、という結論に向かおうとしました。

ところが、このようにすると必要リソース分だけリクエストを投げる必要がありました。これは非同期制御が苦手なJavaScriptにとって非常に困るものでした。そこで考えられたのは、単一エンドポイントという考え方です。単一のエンドポイントに対して、必要リソースをリクエストしてレスポンスで複数受け取るという考え方です。facebookのGraphQLがその最も有名な実装でしょう。(というかこれ以外知りません)

GraphQLはRESTよりも絶対優位かというと、そうとは限らないといわれています。なぜかというと、一番の問題は、複雑な状況では極めて非効率なリクエストになってしまうということです(ページングとネストとorderなどを組み合わせた特殊な状況で、適切なSQLが叩けるかどうかを考えてみてください)。他に、HTTPの基本を無視しているのでブラウザ上でデバッグがしにくい等のやんちゃな問題がありますが、これは頑張れば解決可能な話です。しかし前者は、単一エンドポイントとクエリインターフェースで成そうという試みにおける根本的な困難が露出しています。

表題のInstruction Passingにふれる前に、そもそも「なぜこのような問題が、第一世代では起きなかったのか」ということを考えます。

サーバが返していたHTMLというのは、ユーザのインターフェースそのものを表しています。この中には、RESTでは一度に取得できない情報が多くありました。例えば、ログインしているユーザの名前、最近の通知、今の検索条件にマッチするリソース等。つまり、第一世代は、すでに複数のリソースを渡していた、と考えることが出来ます。REST APIのような考え方の厳しさはこの点にあると考えられます。

では、GraphQLが起こしたような、コーナーケースにおける非効率は起きなかったのかというと、"起きたけど、チューニングの対処がしやすかった"のです。なぜなら、ページにアクセスしたときに呼ばれるアクションは決まっていて、何が評価されるのかは一直線でつながっていて予測しやすく、単純だったのです。

私はこの第二世代のRESTやGraphQLに足りておらず、第一世代のHTMLに足りている、"ある点"に着目しました。それが「命令(Instruction)を渡している」という点です。対照的に、第二世代のRESTやGraphQLがクライアントに対して渡しているのは、データ(Data)です。RESTやGraphQLのことを、 "Data Passing" と呼び、これに相対する形で "Instruction Passing" という概念について考えました。

「HTMLもデータであろう」というのは全く以て正しいご意見です。注目することは、HTMLというデータによって表現したい対象そのものです。例えば、Aタグやフォームのsubmitのような操作対象のリンクを考えてみてください。ユーザは、この与えられた操作対象を操作しますね。ユーザは意図して操作しているので、"命令"と言うと強すぎる単語ですが、"説明"あるいは"指図"と表現すればイメージが付きやすいと思います。Instructionの正体は、このように"ユーザ自身の遷移を促すこと"自体であると考えます。

これをSPAクライアント向けのサーバで成そうとしたらどうなるのか、というのが、Instruction Passing構想の中心です。私は、この命題の1つの解である実装にたどり着きました。その実装が ServerComponent です。簡単に言うと、RESTに近いですが、JSONの代わりに、クライアントが実行するべきコードをサーバが返します。

サーバ返されたコードは、クライアントアプリが適切な箇所で評価し、特定の状態へと遷移します。したがってクライアントアプリ側はどのような状態になるのかを、完全にサーバに委ねることになります。私はこれまで何度かSPAアプリケーションを、クライアントとバック両方を跨いで書いてきましたが、それまで試したどのような方法よりも、シンプルに分かりやすくコードを記述できます。*2

(ServerComponentは今の所Reactをベースにしていて、"命令"は、対象となるコンポーネントのsetStateで終了するようなJSコードになります。)

この一見横暴な方法ですが、実際にはRESTやGraphQLで起きたような問題をきれいに解決してくれます。実際、リソースベースに切り分けることは要求しません(代わりに、コンポーネントで切り分けることが要求されます)ので、RESTの問題は生じませんし、評価されるアクションも明確なので、GraphQLが苦手とした特異な状況での対応も簡単です。

最後に、Instruction Passingという構想が実現するとどうなるのか、というと、これは予測的なことですが、まず間違いないことは2つあります。1つはコードが短くなります。そして、バックエンドとフロントエンドにロールが分割されている状況下における仕様調整の時間が短くなります。さらに、注意さえすれば、ドメインロジックをサーバ側に置くことが自然と要求されるようになり不必要にフロントエンドが肥大化することを防ぐことが出来、システム全体に置ける機能の再利用性が高まります。一方で、サーバサイドは命令の返却に伴う技術を洗練していく必要性が生じると考えられます。

*1:直接の利用者は本当は常に"ブラウザ以下のレイヤー"ですが、ここでは隠蔽されているものとします。

*2:この辺を見てみてください: https://github.com/effective-spa/server_component_rails#-getting-started