新卒エンジニアが語る!ワールドリンクイベントのAPI開発裏話
はじめに
この記事は Colorful Palette アドベントカレンダー 12/8 の記事です。
こんにちは、株式会社Colorful Paletteでサーバサイドエンジニアをしております、ジョニーです。
Colorful Paletteでは、「プロジェクトセカイ カラフルステージ! feat. 初音ミク(以下、プロセカ)」の開発・運用を担当しております。
今回は、2023年11月に初開催されたプロセカの新形式イベント「ワールドリンクイベント」のAPI開発裏話を語りたいと思います。
技術的な内容を含みますが、エンジニア以外の方にもある程度楽しんでいただける記事を目指します。(技術用語が分からん!とかはご愛嬌で…)
自己紹介
今回初投稿ということで、軽く自己紹介をさせていただければと…
ワールドリンクイベントについて
概要
さて、本題に入る前に、ワールドリンクイベントについて補足します。
ワールドリンクイベントとは、ゲーム内ユニットのキャラクター1人1人に順番にスポットがあたっていく新形式のイベントです。
これまでのプロセカのイベントは、特定のゲーム内ユニット、あるいは複数人のキャラクターに対してスポットを当てる形式のものでした。
ワールドリンクイベントも特定のユニットにフォーカスする点はこれまでと同様ですが、イベント期間がいくつかの「チャプター」に分けられており、各チャプターでは特定のキャラクターにスポットが当たります。
「ランキング」「交換所」といった仕組みはこれまでのイベントを踏襲しつつ、チャプター単位でもこれらが設けられています。まさに「推しへの愛」を全力で注げるイベントになります。
サポートユニットについて
ワールドリンクイベントでは、これまでのイベントにない仕組みとして「サポートユニット」というものがあります。これは通常のユニットとは別にチャプター単位で編成できるユニットになります。
細かな仕様の説明は省略しますが、サポートユニットにはメインユニットと同じメンバーは編成できないようになっています。この仕様が今回の裏話のミソになります。
補足:APIとは
以降の内容においては技術用語がところどころ出現します。なかでも出現頻度の高い「API」という用語について簡単に解説を入れておきます。
APIは非常に広義な用語なので、本記事における用途に限定してお話しします。本記事におけるAPIとは、スマホアプリからデータを受け取り、全てのユーザーに共通のストレージ(データベース)に保存する役割を持つものを指します。
ゲームで使用するデータが全てスマホに入っているわけではありません。ソーシャルゲームではさまざまなタイミングでAPIを実行し、必要なデータをデータベースから参照したり、データを保存したりしています。
プロセカでみんなでライブをする際、同じルームに参加したユーザーのニックネームや称号が確認できますよね。これが実現できるのは、全てのユーザーの情報が共通のデータベースで管理されているためです。
開発初期のAPIデザイン
ワールドリンクイベントの開発において、私はサポートユニットの編成部分を任せていただくことになりました。そのため、まずは既存のユニット編成の実装を調べるところからスタートしました。
調べた結果、ユニット編成まわりのAPIデザインはおおむね以下のようになっていました。
通信量を削減するため、メンバー編成やメインユニットの変更をしてもすぐには通信を行わず、ホームに戻る際に最終的な編成情報だけをまとめて送り、データベースに保存していました。
さらに、編成情報を保存するAPIには、ユニットのメンバー編成情報を保存するユニット編成APIと、メインユニットがどのユニットであるかという情報を保存するメインユニット変更APIの2種類が存在しており、必要なAPIだけを叩く方式になっていました。
そこでこのデザインに追従し、以下のようなAPIデザインを考えました。
サポートユニットのメンバー編成情報を保存するAPIを新たに用意し、既存の2つの編成系APIと同様にホームに戻る直前に必要に応じて叩くというデザインです。シンプルですね。
さて、このデザインは一見問題ないように見えますが、何が問題なのでしょうか。”全ての通信が正常に行われれば”問題はありません。しかし、例えば通信トラブルで一部のAPIを叩くのに失敗した場合はどうでしょうか。
具体的なケースを考えてみます。
メンバー編成画面に遷移した際、もともと以下のメインユニット、サポートユニットが編成されていたとします。
メインユニットをユニット02に変更し、ユニット02のメンバーと重複しないようにサポートユニットのメンバーを以下のように再編成しました。
(オレンジの枠で囲んだ部分のメンバーが変わっています)
ホームに戻る際、本来であればメンバー編成API、メインユニット変更API、サポートユニット編成APIが叩かれますが、通信トラブルによりサポートユニット編成APIだけ叩くことができませんでした。この場合、メインユニットとサポートユニットのメンバー編成は以下のようになります。
(※ 実際のゲーム画面ではなく、前掲の画像を仮で組み合わせています)
お分かりいただけたでしょうか。オレンジの枠で囲んだ部分で、メインユニットとサポートユニットのメンバーが重複してしまいました。APIを複数にすると、データベースのトランザクションを張ることができず、データの整合性を担保することができないのです。
整合性を担保するには、メインユニットのメンバーを変更しうるAPIでサポートユニットも一緒に保存する必要があるのですが、該当するAPIがなんと2つ(ユニット編成API、メインユニット変更API)に分かれています。これではどちらと結合したとしてもメンバーの重複を回避できません…。
最終的なAPIデザイン
最終的には、ユニット編成、メインユニット変更、サポートユニット編成の3つを同時に行うことのできるAPIを作ることにより解決しました。
そうなんです。実はワールドリンクイベント開催の裏側で、従来の2つの編成系APIから新たな1つのAPIへの世代交代が行われていたのです。
そもそもなぜもともとの編成系APIが2つに分かれていたのかという話ですが、おそらくREST的な思想に基づいていたのだと考えます。
ユニットのメンバー情報と、メインユニットがどれであるかという情報はそれぞれデータ上は異なるリソースとして扱われているため、別々のAPIで保存をしていたのでしょう。これまではどちらかのAPIが通信エラーで失敗したとしても、データの整合性という意味では問題にはならなかったのです。
学び
REST的な思想に則ると、1つのリソースに対して1つのAPIを用意するのが綺麗で直感的はあると思います。
しかし、今回のケースのように異なるリソース間での整合性担保が必要な場合などは、複数のリソースを1つのAPIで更新するような設計にする判断も必要であることを学びました。
これは少し余談になりますが、設計的な美しさと実用上の問題が競合することは、数ヶ月開発をしてきた中でもたびたび遭遇しました。中でも頻繁なのが、レスポンス速度との競合です。
プロセカのようなソーシャルゲームでは、ユーザーの皆様に快適にご利用いただくために、通信回数を減らしたり・レスポンス速度を上げたりすることが要求されます。今回紹介した編成APIもその一例です。
APIの内部実装においても、レスポンスを高速にするためにできるだけデータベースへのアクセス回数を減らす工夫が行われています。既に一度取得したデータを使いまわしたり、一度の通信で複数のデータに更新をかけたりなどです。このような工夫の過程で、ソースコードの読みやすさが損なわれてしまう場合もあります。よりよい体験を追求するうえでは、時に設計的な妥協も必要なんですね…。
終わりに
まだまだ開発経験が浅く、考慮が行き届いていない部分もあるかと思いますが、現時点での私の所感をお伝えしました。
ゲーム開発やプロセカに少しでも興味をもっていただけていれば幸いです。
ワールドリンクイベント、今後も続々と開催されますので、ぜひお楽しみください!!
最後まで読んでいただき、ありがとうございました!