ゲームの品質を向上させるサーバー共通基盤の作り方
竹端尚人氏(以下、竹端):こんばんは。本日はお集まりいただき、ありがとうございます。
今日は「ゲームの品質を向上させるサーバー共通基盤の作り方」というテーマで、主にソーシャルゲーム開発での共通基盤の作り方について、技術を深掘るというよりは、その作り方や、どう進めていくのがいいか、技術選定の考え方などをお話しできればと思っております。
簡単に自己紹介をさせていただきます。株式会社アプリボットの竹端と申します。
ゲームのサーバーサイドエンジニアで、今まで主にJava、Kotlinをやってきました。最近はUnity C#でクライアントの開発も少しやっています。
エンジニア歴は10年ほどです。2006年に社会人になり、公務員を1年半ほど経験した後、いわゆるSESでSIerなどを渡り歩き、2011年にサイバーエージェントに入社して、そこからゲーム開発を始めました。
2014年の4月からはアプリボットに入社して、エンジニアのマネジメントや、複数のタイトルの開発・運用に携わってきました。なので、キャリアのほとんどはソーシャルゲームなどを作っているといったかたちです。
簡単に会社の紹介もさせてください。株式会社アプリボットは、サイバーエージェントの子会社です。
主にスマートフォンゲームの開発をしていて、一部他の新規事業もやっています。
アプリボットのサービスを簡単に紹介すると、ゲームのタイトルを3つ運用しているのと、いくつかの新規タイトルを開発しています。また、「Nizista」というメディア系の子会社や「QUREO」というオンラインプログラム教育サービスなどを、新規事業として展開しています。
その中で、ゲームで使っている技術セットがこちらです。
それほど珍しいものは使っていませんが、サーバーサイドはやや珍しいですね。KotlinやJavaを使っていたりします。クライアントサイドは、UnityのプロジェクトとCocosのプロジェクトがあり、インフラはAWS・GCPで、一般的なものを使っています。
今日は、このサーバーサイドの共通基盤についてお話をしたいと思っております。アジェンダはこちらです。
共通基盤とは何か?
まずは1個目、サーバーサイド共通基盤についてお話ししていきたいと思います。
「共通基盤とはどういうものか?」ということで、共通基盤という言葉自体がどこまで一般的かはなんとも言えないので、「弊社での共通基盤はこういうものですよ」というものとして聞いていただければと思います。
大手の企業さんでよくある自社製のフレームワークみたいなものではありません。Spring Frameworkをベースに実装を共通化したり、各プロダクトでの実装を簡単にできるようなものを作っています。
その下に書いている「ユーザー管理」や「認証」といった、「どのプロダクトにも絶対あるよね」みたいなものは、あらかじめここで用意しています。
そしてここがポイントです。「StarterKit」と呼んでいますが、今言った実装を共通化したものや、あらかじめ作ってある機能を全部含んだリポジトリが1個用意されていて、各プロジェクトはそれをForkして、プロジェクトを始められるようになっています。
何がいいのかというと、基盤があれば、すでに動かせる状態のプログラムがある程度作られているため、Forkするだけですぐに開発を始められます。
また、実装は再利用されており、別のプロダクトですでに使っていたものなので、品質がある程度保証されています。他には、そもそもその基盤に含まれているものは各プロジェクトで作らなくてよくなるため、工数の削減にもなります。
基盤に含まれる技術
基盤の構成ということで、基盤に含まれる主要技術としてはこういったものがあります。
これが、先ほどお話ししたStarterKitというリポジトリの中で使っている技術ですね。最新のものでは、サーバーサイドごとにSpringBootを使ったマルチモジュールのプロジェクトになっています。
このように「server-〇〇」というものがいくつかあります。これ以外にもありますが、主なものはこちらです。「server-api」といういわゆるController層を持ったモジュールや、「server-core」という、主にビジネスロジック層を持っている部分。他には、管理画面やバッチ処理。「server-db」というのは、ER図やその基盤部分に入っている、ユーザーや認証などの機能のDBの定義などが入っているモジュールになります。
イメージはこんな感じですね。
この「server-core」がビジネスロジックを持っているので、そこに対して、APIや管理画面、バッチ処理、それぞれからそのserver-coreの部分を参照して実装するというかたちになっています。
今日話すのは、この赤枠の部分です。管理画面やバッチ処理のところは置いておいて、主にこのアプリに関わる部分をご紹介していこうと思います。
アプリ部分の構成イメージ
構成イメージはこちらです。
これは設計的にはそれほど珍しいものではないんですが、server-apiのほうにControllerとか、Request・Responseのクラスタがあります。また、Interceptorもですね。先程のユーザ認証等も含め、Interceptorでやる必要のある処理はあらかじめ実装されています。
server-coreでは、この「Service」クラスが主にビジネスロジックを持っています。「Dao」というデータアクセスのクラス……この真ん中の部分ですが、ここの上2つがデータアクセスの処理を作っています。一番右側の囲んでいる部分は、MyBatisというO/Rマッパーを使っていて、その自動生成で作っているクラスなります。
これは関係がわかりやすいように簡略化していますが、実際にはもう少し各モジュールにクラスがあります。。
このserver-apiには、各APIのControllerやInterceptorなどが入っています。またSpringは、比較的アノテーションを使うんですが、独自で作った各種アノテーションも入っています。
server-coreには、ビジネスロジックを持ったServiceクラスや、Utilクラスが入っています。あとは独自の例外クラスを使う場合はここに作っていたり、パラメータの受け渡しとかで使うBeanのクラスもこの中に入っています。
StarterKitに用意されている主な共通機能
StarterKitには、主な共通機能として、ユーザー登録・認証や、クライアントでのデータダウンロードなどが入っています。データベースのデータをクライアントにダウンロードしたり、こういった処理も共通化されています。他には、iOS、Androidの課金などのプラットフォーム課金の購入や消費のところも入っています。
「体力消費」「回復」や「チュートリアルの状態管理」など、このあたりはプロジェクトによってカスタマイズしてしまいますが、基本的なものも一応用意しています。ユーザー情報についても、プロジェクトによってカラム追加が必要になりますが、ベースとなるものは作っている感じですね。
システム的なものでは、こういうデータベースの接続周りだったり、RedisやMemcachedといったキャッシュを使う部分の接続処理だったり。他には、先ほどいっていた認証処理も含めたInterceptorの部分と、ControllerやServiceの実装や設計の共通化ですね。
いくつかの機能は実際に実装されているので、それをサンプルに各プロダクトの機能を実装することもできるようになっています。
StarterKitの使い方
実際にどう使うかですが、これだけです。
StarterKitからForkして新しいリポジトリを作って、Gradleの各種設定だけ変更します。Dockerで動かせるようになっているので、Dockerをインストールして起動します。Gradlespring-bootrunというのは、Springを使う時のGradleのタスクがあるんですが、それを実行すると、これだけでサーバーが立ち上がります。
よって、初期状態ですぐサーバーを立てることができて、かつ、ユーザー登録や認証など、基本的なところはすでにできています。もちろん、クライアント側の実装は必要ですが、サーバー的に必要なものはできあがっていて、あとは各APIをどんどん実装していくだけという状態にすぐに持っていけるようになっています。
文字だけではイメージしづらいと思うので……例えばこういう感じですね。
これはControllerを1個作っているんですが、この「BaseController」というクラスが用意されていて、これを継承してそれぞれの処理を書いて作ってあげれば、Interceptorでの認証処理やトークンチェックといった処理は勝手に行ってくれるため、これだけで実装ができます。
先ほどもお話ししたとおり、Controllerもすでにいくつか用意されているものがあるので、それをサンプルに作ることもできます。つまり、あっという間に実装を始められるというところが、この共通基盤を用意している利点になります。
この章のまとめです。
これは弊社の話ですが、サーバーサイドの共通基盤をStarterKitとして用意していて、これをForkして構成に則って実装するだけで、簡単に実装を始められます。あとは、共通的な機能も作らなくて済むので、開発開始時の工数削減や設計の品質向上を図れる。そこはかなりのメリットがあります。
実際、少し前に新しいプロジェクトを始めたんですが、その時は、すぐに作って実装を始められました。だいぶ楽だったので、これを作っておけば、長い目で見るといいことがあるかなと思います。
技術選定における考え方
続いて、技術選定の考え方です。
一度共通基盤を作ると、基本的にはそのあとはそれを新しく作る全プロダクトで使っていくことになります。ですので、どんな技術を選ぶのかは悩ましいところです。1つの例として、弊社で技術選定を行った際の考え方をご紹介できればと思います。
どんな技術を選ぶかということで、まずは当然、ゲーム開発に適した技術を選びましょう(となります)。なにが適しているかは、もちろんその会社さんによって違うと思うので、どんなものが必要かを定義して、それに適したものを選んでもらえればと思います。基本的には、先々ずっと使っていくものなので、新しくて安定している技術を選ぶ方が良いかなと思います。
ただし、ずっと使っていくといつかは古くなってしまうため、今後スタンダードになる可能性のある……チャレンジする価値があり、可能性があると感じる技術も対応できるとベストかなと思います。
弊社で使っている基盤に含まれる主要技術はこういったものです。
例えば、SpringBoot・MyBatis・Gradleは安定の技術セットです。
SpringBootは、Javaのフレームワークとしては鉄板で、今ではKotlinでもよく使われるようになっています。
MyBatisは、JavaのO/Rマッパーです。もう1つ、「DOMA2」と並んでこの2つがメジャーなので、Javaを使っている会社は、だいたいこのどちらかを使っています。ビルドツールとしても、今はGradleが主流なのかなと思うので、Gradleを使用しています。
逆に、残りの3つはチャレンジの技術セットです。
サーバーサイドの言語としては、世の中的にもまだ実績が少ないKotlinを使っています。テストのフレームワークとしては、JavaのJUnitが主流なんですが、KotlinTestというKotlin製のフレームワークを使っています。
通信についてもチャレンジなんですが、RESTではなくてgRPCを使用しています。これも後ほど説明していきます。
技術的なチャレンジを行う理由
では、なぜチャレンジをするのか? まずKotlinについてです。最近、Androidの開発言語として有名なので、みなさんご存じかとは思うのですが、JetBrainsが開発しているオブジェクト指向言語です。
なぜKotlinという言語を選んでいるかというと、先ほど、「ゲームに適した技術を選ぶ」という話をしましたが、「いわゆるソーシャルゲーム開発で必要な要件とはなにか?」を考えた時に、この3つがあがりました。
1つ目は、突発的な大量トラフィックに耐えうる処理性能。これは、特に運用でイベントを開催した際などによく発生すると思いますが、ある時間に急にアクセスが増えて負荷が高くったりします。
2つ目は、ソーシャルゲームは基本的にずっと運用していくものなので、長期の運用に備えた設計も求められます。弊社でも4、5年運用しているタイトルがあったり、おそらくもっと長いタイトルもありますが、これに耐えられる設計が必要です。
3つ目は、ゲーム限った話ではありませんが、不具合の軽減です。ゲームというのは24時間365日動いていて、常にユーザーさんがアクセスしているので、少しでも不具合が起きるとダイレクトにユーザーさんに影響が出てしまいます。ですので、不具合を軽減していかなければなりません。
Kotlinは、これらを満たしている言語だと思いました。
Kotlinは安全性の高い言語である
まず、大量トラフィックに耐えうる処理性能というところで、Kotlinはコンパイル言語なので、PHPやRubyなどの、いわゆるLL言語と比べると処理性能はわりと高いです。
(スライドの)下の例は、あくまでサイバーエージェントグループにあったものを比較しただけなので、参考程度に見てもらえばと思います。同規模のアプリで、Javaで作っているものと、PHPで作っているものではサーバー台数が半分ぐらいになったり、そういったこともありました。
もちろん言語以外のそもそもの実装の部分だったりも影響していますが、実際問題、レスポンスのスピードやパフォーマンスはかなり差が出るところかなと思っております。
長期運用に備えた設計については、Kotlinは静的型付け言語なので、基本的に大人数の開発に向いています。長期運用している中で関わる人数というのは、どんどん増えていってしまいます。同時にいる人数は少なくても、入れ替わりでいろいろな人が関わっていくので、知らない人が来ても手を加えやすいなど、安定した設計にできることが大事だと思います。
また、Kotlinは比較的新しい言語なので、シンプルでモダンな実装コードを書けるところは効率的で、長期運用に備える意味でも、向いているかなと思います。
不具合の軽減についてですが、Kotlinは安全性の高い言語といわれています。Null安全という機能があったり、コンパイルで防げるエラーが多いところもあるので、比較的安全性の高い言語仕様になっています。
とくに魅力的な機能として、Kotlinはこのように、変数でNullを入れるとエラーになるんですね。
このString型のあとに「?」をつけてあげると、ようやくNullが入れられるようになります。ですので、デフォルトはNull不許可のような状態です。
Null許可の変数でも、このようにするとコンパイルエラーになります。
これは「?」がついているとNull許可の変数ということで、そのプロパティにアクセスするとエラーになります。これにはいくつか対応方法方法があるんですが、一番簡単な方法でNullチェックをするとアクセスできるようになります。
このように、コンパイルでNullチェックをしないとエラーになるので、そうしたNullチェック漏れによる「ヌルポ」が実行時に起きるのを減らせたりします。また、デフォルトがNull不許可のため、そもそもNullの扱いを意識するようになったりもします。他には、コンパイルでNull関連の不備を防いでくれるので、レビューの工数を減らせるという副次的な効果もあります。
世の中での実績はまだ少ないんですが、とても安全な言語なので、Kotlinをサーバーサイド言語として導入しました。
gRPCの概要
gRPCは、HTTP/2を標準でサポートしたGoogle製のRPCフレームワークです。最近はgRPC Webなどが出てきてちょうど話題になっていますが、RESTの次の通信方法の1つとして注目されています。
検証した結果も、RESTよりかなりパフォーマンスが高かったです。問題なのは、Unityが標準で対応していないので、その事例が少ないということ。しかし、パフォーマンスを見るとチャレンジする価値は高いと判断して、gRPCを使うことになりました。
ちなみに、これは今年の3月時点での検証結果なんですが、リクエストとレスポンスのそれぞれの平均時間を見てみると、リクエストは2倍、レスポンスは約4倍ほど、gRPCのほうが速かったという検証結果になりました。
これだけ速いと、大幅なUXの改善につながるだろうと考え、導入する価値はあるなと判断しました。
ちなみに弊社のブログでも同じ表が載っているので、そちらも参考に見ていただければと思います。また、Unityプロジェクトで導入した事例もあまり見ませんが、それに関してもブログがあるので、こちらを見ていただければと思います。
比較的新しく、かつ安定した技術を用いる
KotlinTestはテストフレームワークなので、そんなに話すことはないですが、比較的多機能で魅力的なフレームワークでした。
当初は、Spring Frameworkには対応していなかったんですが、これを検証して他の開発を行っている間にSpringの対応が入ったりと、アップデートが早いです。Kotlin製ということもあって、「Kotlinを使っているなら、こちらを使ってみよう」ということで採用しました。
この件に関してもブログに載っているので、興味のある方は見てみてください。
このように、将来性やチャレンジする価値があるもの……チャレンジするにはもちろんコストがかかるんですが、それに見合った価値があると思った技術は、どんどん使うべきだと思います。
この章のまとめです。パフォーマンスや保守性など、先ほど定義したゲーム開発に必要な要件はあくまで弊社の話なので、もし主要プロダクトで「こういうゲームが多いから、こんな要件が必要」みたいなことがあれば、それに適した技術を選ぶのが大事かなと思います。
また、なるべく新しくて、かつ安定している技術を使うといいかなと思います。
可能であれば、チャレンジする技術も見極めて導入できればベストではないかと思います。新しいものを使うと、エンジニアのモチベーション的にも楽しい部分もあると思うので、そういったところも含めていいかなと思います。加えて、新しい技術を使うと、今回のような勉強会で話せるため、ぜひ使っておくことをおすすめします。