マーダーミステリー制作ツール「ウズスタジオ」の技術構成
こんにちは。株式会社 Sally エンジニアの @piesukeです。
私たちは、マーダーミステリーを遊べることが出来るアプリ「ウズ」と、マーダーミステリーを制作してウズ上で遊べることが出来るアプリ「ウズスタジオ」を開発しています。
今回は、ウズスタジオの技術構成について紹介します。
サービス紹介
ウズスタジオとは、マーダーミステリー制作に特化したエディターツールです。
マーダーミステリー特化と謳っていますが、最近は謎解きやボードゲームなど、様々なゲームを制作することが出来るようになっています。
機能としてはシナリオの制作以外にも、以下のようなものがあります。
- 公開したシナリオの分析
- 売上の確認・申請
- メンバーの権限管理
ですが、シナリオ制作機能とそれ以外では技術スタックが大きく異なるので、今回はシナリオ制作機能の構成について紹介します。
アーキテクチャ全体像
ウズスタジオでのマーダーミステリー制作が完成すると、シナリオデータは最終的に Google Cloud Storage に JSON 形式で保存され、ウズがそのデータを取得してユーザーにプレイ画面を表示しています。
ウズスタジオの詳しい技術構成は以下の通りです。
-
フロントエンド
- Next.js / TypeScript
-
バックエンド
- Next.js API Routes
- Node.js
- GKE (Google Kubernetes Engine)
-
データベース
- MongoDB
-
ホスティング
- Vercel
-
アセット管理
- Google Cloud Storage
-
認証
- Firebase Authentication
データベース
ウズのシナリオデータは JSON 形式で保存されています。JSON の扱いに関しては、①RDB に JSON 型として保存する、②NoSQL データベースを使用する、③ ファイルストレージに保存する、の 3 つの選択肢があります。今回は、パフォーマンスとデータの扱いやすさを考慮して、NoSQL データベースを使うことにしました。
NoSQL データベースには以下の選択肢がありました。
- Firestore
- Realtime Database
- DynamoDB
- MongoDB
元々はパフォーマンスの観点で Realtime Database を使っていましたが、key-value ストアで柔軟でスケーラブルなデータ構造を実現するのが難しく、最終的にクエリの柔軟性や豊富なデータ型を持つ MongoDB を採用しました。MongoDB Atlas を利用することで、クラウド上での運用も容易で、運用コストを抑えることができます。
また、MongoDB は Change Streams という機能を使って DB の変更リアルタイムで更新しています。これによって(一応)共同編集の機能も実現しています。
フロントエンド (Next.js / TypeScript)
Next.js アプリケーションを Vercel でホスティングしています。自社の他サービスも Next.js で構築されているため、知見が蓄積されており、効率的な開発が可能です。
開発当初は App Router が Stable になっておらず、本番環境での利用は推奨されてなかった為 Page Router で開発を進めていました。
その後 App Router が Stable となり、Vercel の動きを見ても Page Router の新機能開発が今後も活発に行われるとは考えづらいので App Router に移行したいところではありますが、社内エンジニアのリソース不足によりまだ 移行を行うことが出来ていません。しかし、なるべく早いタイミングで App Router への移行を行なっていきたいと考えています。
状態管理
マーダーミステリー制作の特徴として、条件分岐や複雑なギミックが多いことが挙げられます。
例えば、エンディングに遷移する時に特定のアイテムを持っているかどうか、特定の選択肢に投票しているかどうかなど、様々な条件を加味してエンディングを決定する必要があります。
そのような条件を設定する時には、json から都度該当のデータを取得・表示する必要がありますが、都度 DB からデータを取得するのは非効率です。そこで、最初にシナリオのデータを全て取得し、そのデータを状態管理ライブラリで保存・加工しておくことで、それぞれのコンポーネントで必要なデータを簡単に表示できるようにしています。
状態管理ライブラリはかなりの数があり、好みも分かれると思いますが、ウズスタジオでは zustand を使用しています。zustand を採用した理由は以下の通りです。
- 元々 Redux を使っていたので、思想が似ている zustand は学習コストが低い
- Redux はコード量が多くなりがちだが、zustand はシンプルなコードで状態管理ができる
- ライブラリが軽量であり、パフォーマンス上の懸念も少ない
- ミドルウェアやサードパーティーのライブラリも豊富
実際に使ってみて、zustand は Redux に比べてかなりシンプルに状態管理が出来る一方で、あまり情報が多くなく、困った時の解決策が見つけにくいというデメリットもあります。
公式ドキュメントは見やすいですが、最低限の情報しか載っておらず、その割に柔軟性が高いので、ベストプラクティスが確立されてない感があります。今後も試行錯誤していきたいと思います。
バックエンド部分
GKE を使って Node.js のサーバーを立て、MongoDB との通信を行っています。
また、API Routes を使って、ファイルのアップロードやダウンロード、データの取得などを行っています。
以前は MongoDB とのリアルタイム通信の部分は realm というライブラリを使用してフロント側で行なっていましたが、最近 Chrome の特定バージョンでクラッシュする問題が発生し、解決の目処が立ちそうになかったのと、元々パフォーマンス上の懸念があったため、サーバーサイドで直接 mongo db sdk を扱うことにしました。サーバーは GKE 上 で Node.js サーバーを立て、Socket.io を使ってリアルタイム通信を行っています。GKE はやりたい事からすると少しオーバースペックな気もしますが、自社の他サービスが GKE で運用されているため、管理コストを下げる意味で GKE を採用しました。
また、現在は API Routes でファイルのアップロードなどを行なっていますが、元々 API Routes はクイックなレスポンスを行う前提の機能で、ファイルのアップロードなどの重い処理を行うのは適していません。そのため、今後はファイルのアップロードなどの重い処理も GKE に移行する予定です。
アセット管理
マーダーミステリー制作では、画像や音声などのアセットが必要になります。そのため、Google Cloud Storage を使用してアセットを管理しています。
セキュリティの観点から署名付き URL を使用して、アセットのアップロードやダウンロードを行っています。
認証
Firebase Authentication を使用しています。ウズとウズスタジオの両方での認証を統一するため、マルチプラットフォームで利用可能な Firebase Authentication を採用しました。
認証部分だけを切り出してパッケージ化することで、弊社の他の Web サービスでも利用できるようにしています。
まとめ
シナリオ制作ツールという珍しいサービスを開発しているため、あまり知見が転がってないような課題に直面することも多いですが、それが逆に楽しいところでもあります。
また、複雑なデータ構造をどう分かりやすくユーザーに見せて、簡単に開発してもらうかという UX の部分を考えるのもとても面白いです。
現在弊社ではエンジニアを絶賛募集中なので、興味のある方はぜひお話しましょう!
Discussion