Skip to content

Instantly share code, notes, and snippets.

@hasipon
Last active December 1, 2015 10:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hasipon/155f3bb80f3a314ee96f to your computer and use it in GitHub Desktop.
Save hasipon/155f3bb80f3a314ee96f to your computer and use it in GitHub Desktop.
2.
ふたたび、話は、WebからNativeへの転換期、
Playgroundでゲームを作りはじめたころにさかのぼります。
3.
そのころ、KLabではガラケー向けブラウザゲームを作っていました。
通信に着目すると、ガラケー向けゲームというのは、
HTTPサーバがあって、そこからHTMLなどを返して、それを携帯のブラウザで表示して、操作するゲームです。
4.
さて、スマホ向けゲームを作りはじめるにあたって、ガラケー向けブラウザゲームのノウハウをいかしたいところです。
そこで、HTTPサーバを使うというところは踏襲しました。
そして、クライアントは携帯のブラウザではなくPlaygroundになります。
また、サーバが返すものは、HTMLからJSONに変わります。
5.
こうすることで、もともとガラケー向けゲームでサーバを開発していた人が
スマホ向けゲームのサーバ開発にそのままうつってくることができます。
また、クライアントサイドの開発は、playgroundになることで、ガラケー向けとは大きく違う新しい領域になりました。
6.
スマホ向けゲームをどのような計画で作ろうとしたか、という話をします。
まず、通信APIを設計して、それにもとづいてサーバ開発とクライアント開発をおこない、
それらのつなぎこみをして、ゲームができあがり、というのが計画でした。
役割分担もわかりやすいですし、スケジュールもたてやすい。
いい計画のように思えますね?
7.
通信API設計とは具体的にはどのようなものかというと、
どのようなURLにどんなリクエストデータを送ったらどのようなJSONが返ってくるか、というのを決めたものです。
サーバ開発者はこの通信API仕様にもとづいてサーバを作りますし、
クライアント開発者はこの通信APIをつかってゲームを作っていきます。
8.
ガラケー向けゲームは、基本的には、サーバでHTMLを生成して、それを携帯が受信して表示して、
そしてポチポチするたびに通信がはしる、というようなゲームでした。
9.
そして、スマホ向けゲームを作るにあたって、
最初は、このポチポチするたびに通信がはしる、というのを踏襲してしまいました。
10.
で、海外向けゲームを作っていたので、サーバをアメリカにおきました。
11.
それで何が起きたかというと通信待ちがひどい。
日本にサーバ置いてたときはこんなんじゃなかったのに、なんでこんなにひどいのか?
っていうぐらいに、通信待ちがひどかったわけです。
12.
根本的な原因は操作するたびに通信しているせいではあるのですが、
アメリカにサーバにもっていったら遅くなったっていうのは、、レイテンシーが違う、というのが原因でした。
ラウンドトリップタイムが180ミリ秒あって、それが待ち時間の大半を占めている、というのがわかりました。
これは光の速度など、物理的な限界があるので、原理的になんともしがたいところです。
13.
とはいえ、このままではだめなので、なんとかして通信待ちをごまかす必要があります。
14.
通信API設計から見なおさないと、根本的には、なおせなさそうに思えます。
15.
しかし、通信API設計をみなおすと、サーバ開発もクライアント開発もからんで、大規模な改修になってしまうことがあります。
16.
ですけど、スケジュール上は開発終盤ということになっていて、
なんとか急ぎで対応できないのか、という話になりました。
17.
なので、大規模な改修をして、抜本的になおす、というのは却下されました。
18.
そこで、短期的成果を得るために場当たり的な対応でなんとかする、ということになります。
そして、なんだかんだいって、表面的にはこれでなんとかなってしまうのです。
ですけど…
19.
この場当たり的対応、積み重なります。
なんだかんだと急ぎで対応したいことがわいてきて、
それらを場当たり的に対応してしまいます。
開発終盤とはいったいなんだったんでしょうか。
20.
しかしこんな場当たり的対応を繰り返していると、
その場当たり的対応がこんがらがって、足かせになって、
ちいさな修正すら難しい、という状況に陥ってしまいます。
どうしてこんなことになってしまったのでしょうか。。
21.
ふりかえってみると、
操作するたびに通信するというのを踏襲したのに、
あとから、通信待ちをなんとかしようとしたのが、
根本的な原因ではないかと思います。
通信待ちをなんとかしたいんだったら、これは踏襲すべきではなかったです。
22.
痛い目をみてしまいましたが、
最初から操作するたびに通信しないように設計したい、という考えにいたったことは
これはこれでひとつのレベルアップといえるのではないかと思います。
この反省をいかして次のレベルを目指していこう、と思います。
23.
さて、通信しないようにするにはどうしたらいいでしょうか?
24.
そんなときには、通信結果をキャッシュしよう、というのがひとつの自然な考え方かと思います。
25.
いろいろな種類の通信APIがあるので、それぞれに対応するキャッシュを用意して、クライアントでデータを保持することで、
通信頻度を下げることができるのではないかと考えられます。
26.
しかし、APIによっては、他のAPIのレスポンスに影響を与えるAPIというのもあって、
それによってキャッシュ間の不整合が発生して、キャッシュの内容が間違っている、というような状況が発生したりします。
27.
クライアントが複雑化していくと、不整合の対応とかでキャッシュの扱いもどんどん複雑になっていって、修正コストが増えていって、
先ほどと同じようにちいさな修正すら難しいという状況に陥っていきます。
28.
ふりかえってみると、
APIの種類ごとにキャッシュしようというのがやっぱり判断ミスだったように思います。
そもそもなんで通信APIの種類ごとにキャッシュしようとしてしまったのでしょうか?
29.
その理由を考えると、通信API設計を先におこなう、というところはそのままでしたし、
キャッシュ実装はクライアント開発の領域ということになっていたので、
クライアント開発としてはAPIの種類ごとにキャッシュするのが自然であり、実装しやすい、という状況がありました。
そのため、キャッシュ実装がサーバ開発から切り離されていました。
でも、APIとキャッシュは密接な関係にあるものであって、それらは統合して扱ったほうがよいのではないかと考えられます。
30.
キャッシュ実装とサーバ開発を統合できないか?という考えにいたりました。
これもひとつのレベルアップだったと思います。
では、具体的にどうしたらキャッシュ実装とサーバ開発を統合できるのでしょうか?
31.
さて、この段階ではクライアントにMVVMが導入されました。
32.
サーバとの通信はMVVMのうち、どれの役割かというと、Modelです。
33.
さて、クライアント開発とサーバ開発の役割分担を考えると、
従来はModelまでがクライアント開発の領域であり、Modelとサーバの間がクライアント開発とサーバ開発の境目でした。
ここで、キャッシュ実装とサーバ開発を統合するというのを考えます。
34.
そうかんがえると、キャッシュ実装というのはMVVMにおいてはModelの役割です。
なので、Modelとサーバの間に境目を置くのではなく、
ViewModelとModelと間に境目を置いたほうがいいのではないか?という考えになります。
35.
そこで、開発体制を変更して、担当範囲を変えてみます。
36.
こうすることで何が変わるかというと、
従来は、通信つなぎこみはクライアント開発が担当する仕事でした。
37.
それが、通信つなぎこみがサーバ開発の仕事に変わります。
38.
また、従来はクライアント開発に提供されるものは通信APIでした。
39.
それが、提供されるものがModelの関数に変わります。
さて、通信つなぎこみをサーバ開発者が行い、
提供するものが通信APIからModelの関数になることでなにが変わるのでしょうか?
40.
そのなかでも最も大きいものが、ViewModelに影響しない通信APIの変更がサーバ開発に閉じるということがあげられます。
通信API設計を変更しても、ViewModelからみて、Modelの挙動が変わっていなかったら、クライアント開発には影響しません。
41.
これによって、通信頻度をへらすための修正がやりやすくなります。
では、ここからは、このようなModelはどのように作っていくか、というのを考えていきます。
42.
従来の状態というのは、通信APIの種類ごとにキャッシュがある、というものでした。
43.
それに対して、キャッシュの代わりに、Modelを作って、これが通信を行う、というのを考えていきます。
44.
そしてこのModelの内側を考えると、
Modelというのは状態と関数からできているものです。
45.
そしてこのModelの関数の役割はなにかというと、
一番大事なことは、ViewModelにデータを渡すことです。
そのために、ViewModelが使いやすいように、データを加工したりする、という役割があります。
46.
そのもととなるデータを得るために、Modelの関数はサーバと通信します。
47.
そして、データをクライアントに保持するためのものとして、Modelの状態があって、
Modelの関数はこの状態を参照したり変更したりして、Modelとしての役割を実現していきます。
48.
さて、このModelの状態というのは、従来のキャッシュとなにが違うのでしょうか?
通信APIがあって、それを記憶するものがある、という点では、あまり変わってないようにも思えます。
49.
しかし大きく変わっている点があります。
それは、設計順序です。
従来は通信API設計が先にあって、それに合わせてキャッシュを設計していました。
50.
現在の方法では、それとは逆に、Modelの状態を先に設計して、それに合わせて通信APIを設計していきます。
そうすることによって、通信内容やサーバの処理というのは効率化しやすくなります。
51.
これによって、キャッシュ実装とサーバ開発を統合できないか?という問題が解決し、
キャッシュを使うのに比べて複雑化もしにくくなりますし、通信も効率化できますし、いろいろな問題が解決できました。
しかし難しい点もあります。
52.
そのひとつめが、
あたりまえのことではありますが、
サーバ開発者もクライアントで動作するコードを書く必要があるという点です。
53.
つまり、サーバ開発者もLuaを覚える必要があります。
54.
まあ、これは、がんばるしかないです。
55.
とはいえ工夫の余地はあります。
純粋なLuaに閉じるようにして、Playground独自機能は使わないようにすることで、学習コストをさげます。
Luaの言語仕様自体はかなり単純なので、覚えること自体はそんなに難しくないかなと思います。
それにサーバ開発者としては、Redisとか、Luaをつかっているものはほかにもあるので、
サーバ開発者にとっても必要な言語のひとつといっても過言ではないと思います。
56.
ふたつめに、くりかえしになりますが、サーバ開発者とクライアント開発者の両方がクライアントで動作するコードを書きます。
57.
つまり、クライアントのなかで、ひとつの機能に同時にクライアント開発者とサーバ開発者の両方が関わるということになります。
58.
そのため、UIとロジックの分離というものがより厳密に要求されるようになります。
59.
これは、そもそも分離していないことがアンチパターンとしてあげられる話なので、
難しいこともありますが、分離していない弊害を避けるためにも、がんばってやっていくほうがいいのではないかと思います。
60.
みっつめに、通信APIの設計の変更頻度が上がるということがあります。
61.
クライアントとサーバのバージョンがかみあわないと、おかしな動作をおこしたりもするので、
クライアントとサーバのバージョンをあわせる必要があるのですが、
変更頻度が上がると、クライアントとサーバのバージョンの対応関係が複雑になる、という問題をひきおこします。
62.
そのため、ソースコードリポジトリの統合をおこないました。
63.
それがどういうことかというと、
従来はアセットとクライアントをひとつのリポジトリにいれて、
それとは別にサーバのリポジトリがありました。
これは従来のクライアント開発とサーバ開発のわけかたに従うものでした。
しかし、アセットはファイルサイズが大きく、クライアントリポジトリがどんどんおおきくなってしまい、
ふくれあがったクライアントリポジトリはブランチ切り替えが遅くなるなど、おおくの問題をひきおこしていました。
また、クライアントとサーバのバージョンの対応関係を人間が判断しないといけないという問題もあります。
64.
そこで、現在はアセットリポジトリを独立させて、クライアントコードとサーバコードのリポジトリは統合しました。
こうすることで、クライアントリポジトリのブランチ切り替えが遅いという問題も解決されて、
対応づいているクライアントとサーバが同じブランチになるようにすることで、
自然に対応関係が表現できるようになります。
リポジトリ統合によって、クライアントとサーバのバージョンの対応関係がわかりやすくなり、問題が解決されました。
65.
また、良かった点として、
当初の目的であった、通信頻度の削減には成功しました。
クライアント側でデータを保持する前提でサーバや通信APIを設計できるので、
クライアントで保持しているデータを積極的に利用することができ、
必要なときにだけ通信する、ということが実現できました。
66.
もうひとつの良かった点として、
クライアントとサーバにまたがる修正が簡単になったので、
従来はクライアントに影響するからといってためらっていたような修正も、
この修正はViewModelには影響しないから大丈夫、というようになって、積極的に修正していけるようになりました。
この処理をサーバからクライアントにうつそう、みたいな修正も比較的簡単にできるようになりました。
67.
また、Modelとサーバにまたがったテストを書きたいという需要もでてきます。
従来はサーバに閉じたテストは書かれていましたが、
それだけだとクライアント側のデータ保持、つまりModelの状態がカバーされません。
そこで、Modelとサーバを統合したテストを導入しました。
これによって、従来は困難だったクライアントとサーバにまたがるテストが書けるようになりました。
これもModelの開発をサーバの開発と一体化して良かったことではないかと思います。
また、このテストを自動化して継続的にまわしていく上でも、Modelを純粋なLuaで書くようにしたことや、
ソースコードリポジトリを統合したことがプラスに働きました。
68.
それでは、通信周りのレベルアップをふりかえります。
まず、操作するたびに通信しないようにするには、最初からちゃんと設計しないと難しいことがわかりました。
つぎに、キャッシュの実装は機能が複雑化するに従って難しくなってくるということがわかりました。
それらの反省点をいかし、Model開発とサーバ開発を統合することで、これらの問題を解決することができました。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment