見出し画像

マーダーミステリーアプリ「ウズ」を構成する技術

マーダーミステリーアプリ「ウズ」の主に開発担当をやっているaitaroです。
アプリリリースから1年経ったので、このタイミングでどのような技術構成になっているかを公開してみたいと思います。​

アーキテクチャ全体像

画像1

できる限り標準的な技術を使って構成して行くことを考えています。
もともと、Flutter,FirebaseAuth,Firestore,Agora という簡単構成でしたが、サービス運用していく上で様々なつらみが発生したので、goでbackendを導入したりしました。
基本的にデータは全てFirestoreでもって、データ更新はgo経由で更新しています。またデータ取得もイベント一覧画面等、一部goでサーバーサイドジョインをしてから取得しています。
もともとゲームのデータもFirestoreで持っている設計だったのですが、ウズ3作目の「Hotel Clue to Magic」あたりで、Firestoreのデータ量制限に引っかかり、Google Cloud Storageから個別にダウンロードする形に変えました。

実稼働するこれぐらいのサービスを作るのは初めてだったので色々手探りで作っていきましたが、その試行錯誤の結果を記していきます。

モバイルアプリ部分

フロントエンド(アプリ)は現在のメインストリームに照らし合わせると以下の4つがあり得ると思いますが、Flutterを採用しました。
・native開発 (swift / kotlin)
・React native
 • Flutter
 • Unity
まず、Unityはゲームエンジンとしての要素が強いですが、マーダーミステリーはゲームとしてテキストベースで、ゲームエンジンをつかう場面が少ないことや今回ウズではイベントの募集やチャットなどSNS的要素も入れたかったので、採用を見送りました。(他のマーダーミステリーアプリであるマダミ屋さんは確かUnityだったと思います)

他の選択肢(ネイティブ開発、React native, Flutter)はできることがほぼ一緒ですので好みの問題ですが、最近勢いがあるということでFlutterを採用しています。
この3社の比較記事はググると死ぬほど出でくるので割愛しますが、ウズで採用しなかった理由は、ネイティブ開発は2回同じ実装するのが鬼ほどだるそうだった、React NativeはReactを(当時)書いたことがなかったとかいう個人的理由です。

音声通話部分

音声通話を実装するにあたって最初の分岐点は自社実装かPaaSを利用するかです。
実はウズを始める前、wh.imという別事業をしていたのですが、その時の知見を活かしました。

自社実装の場合でも、流石にSFUサーバを一からフルスクラッチで実装するわけにもいかないので、OSSを利用することになります。JanusやJitsiなどの有名OSSもありますが、色々触ってて良さげだったのはmediasoupです。ここら辺はQiita記事に書きました。
[Qiita記事]緊急事態宣言中に、ビデオチャットしながらゲームで遊べるサービスを作った話

PaaSを利用する場合、日本で1番初心者に優しいのはSkyWayだと思います。中の人が精力的にQiita記事を書いてくれているので、それ通りにやっていくだけで動くようになります。ただ料金体系がわかりにくいのと、モバイルのサポートが薄い印象だったので今回は採用しませんでした。
他にはagora.io, vonage, twilio, aws chimeあたりが候補として上がりますが、flutterへのサポートや音声通話への料金の観点からagora.ioを選定しました。

現状では利用していませんが、agora.ioは老人や子供の声にVoice Changeする機能もあり、マーダーミステリーとの親和性もありそうなので使ってみたいところです。

シナリオデータ保管部分

各シナリオのテキストデータ等を保管しておく場所です。まず、2つの実現方法、(アプリインストール時に同時にダウンロードし)端末に保存しておく方法とクラウドで保管しておく方法があります。前者のメリットとしてはプレイ時のローディングが不要・システムが簡素化される点、後者のメリットとしては各ストア(AppStore, PlayStore)の審査を経ずともシナリオリリースが可能になる、アプリのバージョンによらずプレイ可能になる点があげられます。

ウズでは掲載シナリオ数に現れている通り、他アプリよりシナリオの追加という点をかなり重視しており、シナリオ追加するたびにアプリの審査提出・バージョン更新を行うのは運営的にもユーザー的にも手間がかかるという点からシナリオデータはクラウドで保管する方法を選びました。

FirestoreではなくGoogle Cloud Storageを選んだのは前述した通りですが、メリットはデータ容量の制限以外にも、静的コンテンツなのでCDNに乗せてゲームデータダウンロードの高速化も期待できます(やってないけど)。

Firestore部分

当初はサーバレス運用だったので、BaaSからサービスを選定しました。一応AWS の AppSync も候補にはあったのですが、宗教上の理由でAWSは使えないので、GCP系列のFirestoreを利用しました。

(開発元が同じGoogleということもあり)、FlutterとFirestoreの親和性はそこそこ高いのですが、アプリ+BaaS特有の問題として以下の課題が発生していました。

・複数バージョンのクライアントが共存する環境で、FirestoreのDB構造変更のコストが高い
アプリの改善を繰り返す中で、DB構造の変更はよくあることですが、DB構造の変更がクライアントのデータ取得更新処理に直接影響を与えるので、
1.移行バージョンを用意
2.旧バージョン強制アプデ
3.新バージョンを用意
といった順序になりかなりの工数となります。

・クライアントサイドジョインによるパフォーマンスが劣化する
Firestoreでは単純なwhereクエリしか発行できないので、例えばブロックしているユーザーが含まれていないイベントのみ取得すると言った複雑な条件はパフォーマンスが劣化します。(これを解決する方法として、データを正規化せず冗長化するという方法が取られることがありますが、処理が複雑になりバグ含有リスクがむちゃ上がります。

これらを解決するため、バックエンドの導入を行いました。

バックエンド部分

上記の問題を解決するため、データ更新系の実装と一部のパフォーマンスが問題となるデータ参照形の実装(イベント一覧画面や、シナリオ詳細のレビュー取得部分)をモバイルアプリ部分から分離しサーバサイドで実装しました。

特にこだわりはなかったので、できる限りモダンな構成になるように意識した結果、go + gRPC + kubernetes とかいう今風構成になりました。また、goサーバでのアーキテクチャはできる限りクリーンアーキテクチャに寄せて実装しました。

レコメンド部分

マーダーミステリーはネタバレ禁止なので、プレイ前にみれる情報は少なく、構造的にシナリオ・ユーザーのミスマッチは起こりやすいです。なので、レコメンドによってシステム的におすすめシナリオをユーザーに表示するのはミスマッチ軽減につながり、重視すべき機能になります。

機械学習を使うということでpythonを用いています。kubernetesを用いてマイクロサービスを構築しているため、サービスごとにgoやpythonなどと言語を変えれる、疎結合になり開発に必要な前提知識が少ない、障害が他サービスに伝播しづらいといったメリットがあります。(このnote執筆時点でもレコメンド部分が落ちているのですが、なんとかアプリ本体等は正常に動くようになっています。)

Firebase Authentication

認証機能を提供しているサービスです。自前認証は面倒・セキュリティの知識が要求される・不具合があったときの影響が甚大といった点からお勧めしません。他の選択肢としてはAuth0やAmazon Cognitoがありますが、ドキュメントや関連記事の充実度、料金の点からFirebase Authenticationに軍配が上がります。強いていうと欠点はLine認証に対応していないぐらいでしょうか。

エンジニア募集中!!

ウズを構成する技術とその選定理由をnoteの記事にまとめてみました。このほかにも通知処理の部分や、管理画面、UZU STUDIOなどの他サービスとの連携などなど書ききれなかった部分はたくさんあります!
ウズではサービス拡大に伴い、手伝ってくれるエンジニアを募集しています。
用途に合わせた、色々なプログラミング言語を学べる環境です。それぞれの言語の経験・未経験問わず、興味があればぜひ採用ページまたはTwitterからご連絡ください!

Twitter: https://twitter.com/uzuappjp


この記事が気に入ったらサポートをしてみませんか?