モデルの共用ダメ説の提唱

はじめに

最近サーバとクライアントを同じ言語にしてみたよ、っていう話を良く聞きます。その理由として「モデルの共用ができる」というメリットが挙がります。これに関して、いやそれ違うだろ、って前から思っていて、もやもやしているので書きます。

本当はQiitaで書きたかったのですが、どう考えてもポエムなのでHatenaBlogにしておきます。

軽い背景解説

「はじめに」が大筋理解できていれば読む必要が無いと思いますが、そうでない人もいると思うので解説します。

所謂Webの世界ではサーバ側とクライアント側で厳密に分けられることが多くなっており、ソシャゲだったら運営側のAPIサーバとスマホアプリ(クライアント)という感じで分けられています。ゲームに必要なデータはAPIサーバから取ってきて、スマホアプリは動きとか絵を管理している感じですね。普通のWebサイトでも単純なAPIサーバからデータを取得してJavaSciprtがレンダリングする仕組みのサイトが増えています。この方がUIの自由度が増すんですね。

で、そのAPIサーバとクライアントの間で必要な通信のデータ構造をモデルとここでは呼んでいます。例えばサーバにPersonという情報があって、Person内にname(文字列)とage(数値)があるとします。Scalaで書けばこんなんですね

case class Person(name: String, age: Int)

クライアントがPersonのデータを欲したら、この形式でJSONのObjectなりMessagePackなりでパックされて送られてくるわけです。

当然この形式になっていることをAPIサーバ側もクライアント側も知っていないといけない訳です。ところが今まではサーバ側とクライアント側で言語の断絶があって、サーバ側はRuby/PHP/Perl/Java、クライアント側はJavaScriptが多かったので、結果的に両方でモデル定義を持つ必要がありました。今までは。

ところが、NodeJSでクライアントの覇者JavaScriptがサーバサイドで書けるようになったり、サーバサイドでも使い勝手良さそうなC#が、Unityとしてクライアントで大々的に使われるようになったところで、モデル定義を1つ書けばいいんじゃないか、という話が出てきたわけです。(Fate/Grand OrderがUnityだからサーバもC#にしているという話がありました)

で、ここでぼくが書きたいのは「モデル定義を本当に共用して大丈夫?」という話です。ようやく追い付きましたね。

本題

モデル定義を共用するのに疑問を持っている理由は「そのモデルが更新されたときの、クライアントとAPIサーバの互換性どうするの」という点に尽きます。

さてサービスが運用に乗ります。機能を追加します。最初に作ったPersonモデルにデータが追加しよう! 良くありますよね?ここでは例でnameとageがあったPersonに、海外展開に備えて言語情報を設定しましょう。めっちゃありそう。langっていう名前の文字列でいいかな。

langを追加するためにはモデルに修正が必要です。共用のモデルなら1箇所にlangを追加すればいいだけですね。さてリリースしましょうか?

このとき、もしクライアントとサーバが同時に変更してリリースが可能であればうまくいきます。それは大変幸運で、そして現代においては稀有なパターンだと思います。1つのAPIサーバに1つのJSクライアントしかなければ、まぁうまくいくかもね?

つまり、APIサーバは運営が自由にアップデート可能なことが多いですが、クライアントはそうではなく、古いモデルでやりとりをすることが多々あります。クライアントがスマホアプリだったら更新サボってる人も多そうですし、APIサーバを公開していて多数のクライアントが付いてる場合だと更に困難ですね。全員に更新が行き渡るのに何年掛かるの?TwitterBasic認証やめるのに何年掛かったっけ?

結論として、APIサーバは最大公約数的なモデル定義をする必要があり、クライアントにその必要はなく、それによる弊害の方が大きいと考えています。APIサーバがクライアントからPersonの投稿を受け付けるときはlangがある場合と無い場合がありえるので、両方のパターンを記述する必要があります。クライアントはサーバが自分の持っているPerson型を受け付けられない可能性を考慮する必要はありません。モデル定義さえ別ならば。langという余計なデータが来ても捨てればいいのです。

モデル定義を共用化するためにクライアントのコードを不必要に複雑にする必要はありません。モデル定義のコストとか多寡が知れてるし、2つぐらい作ったらどうですか、という話です。どうせサービス運用がうまくいって3年とか経てば全くの別物になっているのですから。

MyFleetGirlsの経験について

実際あったかのように語りましたが、実際にあったんです、MyFleetGirlsで、という話です。

MyFleetGirlsという自作の艦これツールがありまして、ScalaサーバとWebクライアントとScalaクライアントに分かれています。艦これの通信をMyFleetGirlsのサーバに横流しするのがScalaクライアントの仕事で、サーバと同じ言語なので、この通信に使われるモデルは共用化されています。安易に共用化しました。

結果として、後から追加されたモデルデータは全てOptional、存在するか分からない型として処理されるか、古いクライアントからのアクセスを拒否する仕様になりました。サーバが古いクライアントと通信する可能性があるからです。Optionalになった場合、クライアントは常に値があることが保証されているにも関わらずOptionalを強要されるため、コードからのスメルは強烈なものになりました。古いクライアントからのアクセスを拒否する仕様のために、強制的にクライアントをアップデートする仕組みを1から作るハメになりました。