ゲーム専用のマイクロサービス課金基盤QuaPayを独自開発して得たノウハウの全て
はじめに
株式会社QualiArtsでUnityエンジニア兼バックエンドエンジニアをしていた石黒です。 入社以来6年ほどQualiArtsでゲーム開発において必要な基盤などを開発してきましたが、つい最近サイバーエージェントのSGEコア技術本部という組織の立ち上げのためにQualiArtsを抜けることになりました。 QualiArtsを抜ける直前まで開発していた、自分の過去の経験や知識などを全て投入した課金基盤QuaPayについて今年はアドカレの記事を書きたいと思います。
QualiArts Advent Calendar 2021の16日目の記事です。 前日は@sune2によるUnityで独自の設定のUIを提供できるSettingsProviderの紹介と設定ファイルの保存についてでした。
さて本記事はタイトルの通り、QualiArts初の課金基盤QuaPayの開発秘話とともに、そのノウハウを惜しげなく書きます。 課金機能を開発している方はもちろん、基盤などを開発している方などの少しでもお役に立てられれば幸いです。
QuaPayとは
QuaPayはIDOLY PRIDEのゲームで利用するために開発した、ゲーム専用のマイクロサービス課金基盤です。 近年はマイクロサービスも当たり前の時代で、課金基盤も珍しくはありませんが、 QuaPayはゲーム専用とすることで機能はもちろん設計などの細部に至るまでチューニングしています。 その主な機能は以下の通りです。
- ゲームを遊ぶユーザーの、有償・無償のゲーム内通貨の管理
- 付与:無償通貨の配布、アプリ内課金を通じた有償通貨の購入
- 補填:無償・有償通貨の付与で問題があった際の補填
- 消費:保有している通貨の使用
- 失効:アカウントBANなど
- 返金:アプリ内課金を払い戻した際の有償通貨の回収
- 管理系機能
- ユーザー履歴の取得
- サマリの出力
課金基盤というと何かしらの決済機能を想像されるかもしれませんが、ゲーム特化の場合はどちらかと言うと銀行の方がイメージとしては近いと思います。実際のIDOLY PRIDEでも、ユーザーが保有しているゲーム内通貨の管理を主な役割としています。
そんなQuaPayの技術スタックは、IDOLY PRIDEの技術スタックに合わせて、Golang、gRPC、Google Spanner、モノレポなどを採用しています。
課金基盤の独自開発を判断するまで
QualiArtsではAmeba Games時代を含めてたくさんのゲームをリリースしてきました。 実はそのたくさんのゲームを支えてきたCAグループ内で広く利用している共通の課金基盤が何年も前から存在しており、 自分たちでは運用していない状態でした。 そのため課金機能の開発に関する工数の削減はもちろん、課金機能に必要な泥臭い法務対応や経理対応を自分たちで行う必要がなく、運用の負荷はとても低くそのメリットを最大限享受していました。 そのため一見すると新たに課金基盤を開発する必要がないように感じるかもしれませんが、ご想像の通り運用の長さから課金基盤自体が技術負債となっている部分もありました。
共通の課金基盤における様々な課題
その課題の1つがアジリティです。運用の長い共通基盤でありがちな問題として、リリース初期ほど開発が活発でなくなるということがあります。 そこには様々な理由があるとは思いますが、これ自体は避けることが難しいと思います。 そのため例えばゲーム開発において新たな仕組みを導入するために課金機能の追加開発が必要となったとしても、その開発を依頼して欲しいタイミングまでにリリースされることは難しくなります。 このスポード感が目まぐるしい開発競争の起きているゲームビジネスにおいてはリスクとなってしまいます。
また時代の流れとともに課金におけるビジネス要件も絶えず変化し、都度その要請に応える必要がありました。例えば昔は有償や無償通貨を区別なくユーザーに表示していたのが、今では当たり前の用に区別して表示し、有償通貨限定の商品などもあります。またサブスクリプションなどの月額課金も近年のトレンドと言えるでしょう。そのようなトレンドの変化に柔軟に対応していく必要があります。
ここまでは開発における課題でしたが、他にも共通基盤ならではの運用の問題があります。それは共通基盤が単一障害点(SPOF)になり得るということでしょう。障害が発生しないということは保障できないため、障害を前提とした設計を行う必要があります。ところがひとたび共通基盤、特に重要な課金基盤で障害が発生した場合、たちまちそれを利用している全てのサービスを止めないといけなくなってしまいます。つまりゲーム会社としてはビジネスが完全に停止してしまうリスクを抱えてしまうことになります。もちろん障害を最大限最小化する努 力は行いますが、その基盤が自分たちのコントロール下にないとただ祈るだけになってしまうでしょう。
次に意外と無視できない問題として通信レイテンシの課題が挙げられます。共通基盤とそれを利用するサービスのネットワーク設計にも依りますが、通常はある程度ネットワーク上の距離が存在します。そのため基盤のAPIを1回叩くだけでも100-200msかかってしまうと、ユーザーの快適なプレイを阻害してしまいます。特に課金だけ共通基盤化しているような状況の場合、ゲーム内通貨の獲得や使用時だけユーザー体験が低下してしまい、ユーザーに悪い印象が残ってしまいます。
最後にゲームならではの課題として、外部展開の難しさがあります。開発規模の大きいゲームの場合、海外展開のためにパブリッシングを行ったり、また長期的なサービスの運営のために事業を移管するケースがあります。そのような時に重要な機能が共通基盤となっている場合、外部に展開する時にその基盤を切り離すなどの対応が必要となります。これは移管時の大きな障害となってしまいます。
内製での課金基盤を決断
そのような課題を解決するためにQualiArtsは長期での技術戦略を見据えて、IDOLY PRIDEへの導入をターゲットに課金基盤を内製で開発することを決断しました。QualiArtsでは基盤を重視する文化があるため、課題を解決しつつも単純な基盤ではなく新しい基盤の形を模索することにしました。それが分散マイクロサービス(自称)基盤です。
分散マイクロサービスとは
近年はマイクロサービスで設計する事例が増えてきています。もちろんマイクロサービスは銀の弾丸ではないので闇雲に導入することはよくありませんが、それでも今回はゲームでの事例が少ない中でQualiArts初のマイクロサービスとして設計しました。
通常マイクロサービスとして設計する場合、課金機能を担うサービスを全てのゲームから接続できるように設計します。これはわざわざ同じサービスをそれぞれのゲームで運用すると運用コストがゲームの数だけ増えてしまうからです。ところがこのような設計はさきほど述べた共通基盤の問題そのものであると考えています。そこでQuaPayでは分散マイクロサービスという構成を採用しました。 分散マイクロサービスでは、課金マイクロサービスを単一サービスとして運用するのではなく、それぞれのゲームで運用する形を取ります。そのため分散マイクロサービスと筆者は呼んでいます。運用コストが増えてしまう問題はありますが、それ以上に共通基盤の形を取らないことによりパフォーマンスや外部展開のしやすさなどメリットが上回っていると考えています。
ところで運用を分散化させる狙いは上記のとおりですが、純粋にマイクロサービス化する必要があるのか疑問に思うかもしれません。課金機能をライブラリとして提供したほうがより良い可能性があるからです。特に課金機能の場合、マイクロサービス化することでトランザクションがゲーム側と分離されるため、ステータスの不整合が発生する可能性が出てく るため扱いにくいものとなることが最大のデメリットとなります。例えばゲーム内通貨を使用してガチャを回した時に、ゲーム内通貨だけ消費されてガチャが回らなかったといったイレギュラーが発生する可能性あります。それでも筆者はマイクロサービス化することしたほうが良いと考え、その理由としては2点あります。1つは課金サービスの独立性を担保できることです。課金機能はユーザーの財産を預かる以上、極めてその信頼性が重要となります。もしライブラリとして提供した場合、バグなどの誤った実装によるエラーが課金機能まで影響し、課金機能の信頼性が低下しかねません。もう1つのメリットはゲーム側とは異なるアーキテクチャを採用できることです。今回QuaPayはIDOLY PRIDEと同じアーキテクチャを採用していますが、今後のゲームでも同じアーキテクチャを採用するとは限りません。仮にGo言語を利用しなくなった場合、ライブラリ型の場合は課金機能を再実装する必要が生じてしまいます。
その他の技術的特徴
分散マイクロサービス以外にもQuaPayでは様々な箇所で拘った設計を取り入れているため、その内容と評価について紹介します。
購入毎の管理の分離
素直にゲーム内通貨の管理機能を実装する場合、最低限無償通貨と有償通貨を分離しなければいけないことは明白なため、データベースに分けて保存すると思います。ところが実際には何円で何個のゲーム内通貨を購入したという情報を購入毎に分離して保存しています。使用する際も残っているゲーム内通貨のうち、一番古いものから使用するようにしています。一見無意味と思うかもしれませんが、厳密な売上処理を経理で行う際は必要となるものです。ただし海外からの購入をどう扱うのか、といった部分の課題はあるため、実際には完璧に正しく処理することはかなり難易度が高いので、ある程度のみなし運用はあります。実際この設計にして良かったとはまだ感じられていませんが、後から設計を変えることはできないので細かく管理しておくにこしたことはないと考えています。
完結型API
課金系のAPIの場合、通常はトランザクションの生成と確定処理を分けて実装する例が多いと思います。これはあらゆる場面でのエラーを考慮する場合は正しい設計ではあるのですが、このパターンの場合は最低でも2往復のAPIが必要になるため時間が余計にかかってしまうのと、ゲーム側が機能を使用する際の扱いが難しいと考えました。そこでQuaPayではトランザクションIDをゲーム側で自己生成してもらうことで、1度のAPIで処理を完結させられるように設計しました。これによってパフォーマンスと実装のしやすさを達成できたと考えます。
ところが開発終盤になってゲーム側である程度トランザクションステータスを管理する必要が生じてしまったので、結果的には管理が複雑となってしまいました。これはDBのトランザクションがゲーム側と分離されている影響が大きいです。課金側でトランザクションステータスを管理するようにすればゲーム側で保つ必要がなかったかどうかというのは難しい部分ではありますが、少なくとも二重に管理することは回避できているので良しとします。
わりきった冪等性
APIを実装する上で大切になるのが冪等性です。つまり同じパラメーターでAPIを何回実行しても、結果が変わらないということです。これは特に課金機能の場合、多重付与や多重消費を防ぐためにも重要です。 これについては、上記の通りQuaPayでは1APIで処理が完結するように設計されているため、特に重要です。万が一エラーが発生した際には、ゲーム側は同じパラメーターで更新系のAPIを実行する可能性があるからです。
そこで同一トランザクションIDによる連続操作に関しては、リトライ処理として扱いエラーとせずに前回の処理結果をそのまま返すようにしました。こうすることでゲーム側は気軽にリトライ処理ができるようになりました。もちろんもし連続していない場所で過去使用済みのトランザクションIDが来た場合は、使用済みエラーとしています。 これについては狙い通りの効果は得られましたが、そもそもゲーム側でリトライ処理を記述しないとただただエラーとなって終わってしまうため 、想定したほどの結果とはなりませんでした。
完全なログと過去の状態遡及
スマホ向けゲームにおけるゲーム内通貨は、法律上は前払式決済手段として扱われるため大規模なゲームでは必ず供託金を法務局に収める必要があったり、そのため課金=売上ではなく、あくまで消費したタイミングで売上とみなされるために正確に記録・管理する必要があります。通常のサーバーアプリ開発においてはログをデータベース以外に置くことが主流ですが、このパターンの場合は欠損の可能性を完全になくすことは極めて困難です。そのため課金機能においても同じパターンを採用して万が一ログに欠損が生じると、残高の不整合となり難しい対応が必要となる可能性が生じます。
そこでQuaPayでは更新系のAPIのログは同一トランザクションでデータベースに保存する設計とし、ログの欠損を防いでいます。データベース費用はもちろんかかりますが、課金の重要性を考えると必要経費だと思います。またログを完全に残すことで、任意時点の過去の状態を訴求できるようになりました。つまりユーザーの口座履歴が完全なのでトラブル対応で威力を発揮します。またゲーム全体の口座残高を計算する際も、過去の任意時点においても再集計が可能となるなど副次的なメリットは大きいです。
ノーコードな管理画面
近年流行りのノーコードに乗っかっているわけではないのですが、QuaPayの管理画面はGoogleデータポータルを利用しているためノーコードで開発されています。データポータルを採用した理由は2つあります。1つはそもそも基盤チームで管理画面を開発するリソースがなかったという懐事情に依るところです。もう1つは管理画面を開発したとして、そのアプリを提供してゲーム側で動かしてもらうのか、基盤側で管理画面だけを運用するのか、分散マイクロサービスがゆえに運用コストを考慮すると厳しいと感じたからです。幸いにもDatastudioがSpannerと接続してクエリ集計が可能だったため、これを利用することでこの問題を同時に解決できたので、結果的にはベストな着地点だったと思います。最大の難点がダッシュボードのコード管理が一切できないということぐらいです。
レシートのオフライン検証
AppStoreやGooglePlayのアプリ内課金機能の開発をされたことがある方は馴染みが深いと思いますが、プラットフォーム側で課金をする場合はその証明となるレシートをユーザーから受け取ることになります。このレシートはプラットフォーム側の秘密鍵を利用して署名が付与されているため、改ざんができないものとなっています。そのためこのレシートが改ざんされていないかどうかをチェックするのは必ずやらなければいけない処理です。
レシートの構造や署名の検証方法などがプラットフォームによってことなりますが、AppStoreとGooglePlayの場合、近年はプラットフォーム提供のAPIを通じた検証と結果の取得を推奨しています。これは検証自体の精度の担保と、柔軟なレシートの運用などを考慮した結果だと思います。ところがAPIを叩かないといけないということは通信による処理待ちの時間が発生することや、API自体のエラーも適切にハンドリングしないといけないなど、トータルで考えるとマイナスの方が大きいと感じています。
そこでQuaPayではレシートの検証をAppStore、GooglePlayともに完全にオフラインで検証するように実装しました。GooglePlayに関してはAPIが提供される前は各々で検証する方式だったので難しくはないのですが、AppStoreは当初からAPIでの検証が提供されていたのと、オフライン検証の事例自体が少なくリファレンスもないため苦労しました。最終的にIDOLY PRIDEで採用して今のところ問題は起きていないため、オフライン検 証に対応して良かったと感じています。
テストと品質担保
サーバーを持たない基盤の開発はテストが一番苦労するかもしれません。開発環境はもちろんないため、新機能の検証も一筋縄ではいきません。とは言え開発したものが正しく動くかどうかをゲーム側に検証してもらうのも、やり取りの手間を考えると非効率です。そこでQuaPayでは、GitHubのプルリクを通じてE2Eテストを幾つものシナリオに対して実行することで品質を担保するようにしています。もちろんgRPCとデータベース部のSpannerをモッキングせずに、実際のものを使うようにしています。これはE2Eテストを充実させることで全体の品質を効果的に改善できること、リアルな環境での動作試験に繋がること、また課金基盤の性質上複数のAPIを実行した上での結果整合性が重要となってくることを判断した結果です。
さいごに
QuaPayの技術設計や取り組みに全体の中から一部を紹介させて頂きました。ここに書いていない細かい工夫ももちろん随所に取り入れていたりします。QuaPay自体はまだまだ完璧なものとはほど遠いのでこれからも随時開発は必要ですが、課金機能の開発に関して少しでも多くの方の助けになれば幸いです。
それでは明日は@s9iさんの記事となります、お楽しみください。