忘年会を盛り上げろ！余興アプリ開発の裏側

# はじめに
### これはなに
[NSSOL Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nssol)の12月23日担当の記事です。忘年会の余興のためにクイズアプリを開発したので、その制作過程を共有します。本記事は開発時に主に私が担当したバックエンド、インフラの部分にスポットを当てています。素晴らしいフロントエンド、UI部分については一緒に開発をした同期が書いた[後日公開予定のこちらの記事（2025-05-02追記：リンクを貼りました！）](https://zenn.dev/rei_osally/articles/91bfc0bf6e5720)を参照してください。
なお、コードについては現状公開ができるか怪しい状態です。公開できそうであれば公開します。
### 自己紹介
今年新卒でNSSOLに入社し、忘年会幹事を任された社員です。普段は組合せ最適化に関する研究開発を行っています。余興アプリは同期3人で作りました。
### 背景
新入社員の恒例行事、忘年会の余興。コロナ以前は新入社員たちがアプリを制作して披露する伝統があったとか。その伝統を受け継ぐべく、同期の一人が「アプリをつくりたい」と意気込んでいました。その話を聞いたのは9月のこと。私が暇そうにしているのを見かけた彼から「一緒に開発しよう」と声がかかりました。考えてみれば、3か月もの期間があるなら、1週間のハッカソン以上のアウトプットができるはずー。そう安易に考えて引き受けました。

新入社員としての通過儀礼、この「社会の洗礼」に私も今まさに向き合うことになったのです。


# アプリ概要
### アプリの目的、ルール
このアプリは、約200人が参加する忘年会において、参加者全員がリアルタイムで楽しめる体験を提供することを目的に開発しました。クイズ形式のゲームで、以下の特徴的なルールを設けています：
- 答えが複数あるお題が出され、参加者が自由に回答する。
    - 例：都道府県、ひらがな
- 同じ答えをした人数が少ないほど高得点が得られる。
    - 例：単独正解:100pt, 二人被り:50pt, 4人被り:25pt
    - 「回答のユニークさ」を競うこのルールにより、参加者から笑いや意外性を引き出すことが狙いです。
- 新入社員と同じ回答をするとボーナス得点が得られる。
    - 普段は皆リモートで顔を覚えてもらう機会も少ないため、この機に新入社員を知ってもらうことが目的です。
- クイズは全6問で、合計点を競う。
    - 最終結果はランキング形式で発表され、上位者には景品が渡されます。景品があるので「ひらがな」のような簡単なお題でも読み合いが発生し楽しく参加してもらえると考えました。
### 機能の簡単な説明
上の目的、ルールに沿うべく以下のような機能、構成にしました。1から5はユーザーのスマホ画面、6は幹事のPCブラウザ画面、7は忘年会時に会場前に投影されるスライドのイメージです。それぞれの画面についての簡単な説明が続きます。
![クイズフロー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705827/fcaee3b0-c68f-f768-10f1-130a80933e65.png)
###### 1. 入室画面
- 姓、名を入力、チームをプルダウンから選んでもらいます。
###### 2. 待機画面
- 入室後に見る画面です。ルール説明が書いてあります。
###### 3.出題画面
- お題出題&回答提出画面です。下の入力欄に回答を入力、確認、送信が行われます。
###### 4.結果確認画面
- 各問題について、その結果を確認できる画面です。基本的に3と4の画面を行き来する形になります。自分と同じ回答をした人が何人いて何位なのか、答えた人が多い回答は何だったのか等を一目で確認することができます。出題画面、この画面共に画像以上に多機能で面白いため、詳細、こだわりは[後日公開予定のこちらの記事（そのうちリンクが貼られる）](そのうちリンクを貼る)を参照してください。
###### 5.全回答結果確認画面
- 全員の全問題の回答結果が確認できる画面です。余興で景品を渡している最中でも全ての参加者が楽しめるように作りました。
###### 6.余興進捗管理画面
- 何問目の出題/結果確認画面をユーザーに見せるかを制御する画面です。イベント進行に合わせてボタンを押していきます。集計もこの画面で行います。
###### 7.最終結果画面
- 得点上位5人が表示されます。admin側で集計しブラウザに表示させたのちスクショを撮りスライドに貼る形でユーザーに見せました。

私がつくった画面は業務感溢れる6の画面だけであり、それ以外の楽しさ溢れる画面は同期が作ってくれました。そのセンスに脱帽するばかりです。
### 使用技術スタック：
上の画面、機能をつくるべく以下を用いて開発を行いました。
- フロントエンド:
    -  React
        - 同期の勉強モチベが高かった
        - 新人研修ではhtml, cssで素敵な画面をつくっていたことから、ちゃんと業務で使うライブラリを使いたかった
- バックエンド
    - FastAPI
        - 各ユーザーの回答結果について自然言語処理をする話が一時期あったため、バックエンドはpythonを使うと良いと判断
        - Flask、FastAPIで悩んだが、SwaggerUIの便利さからFastAPIを採用（APIをたたくという行為に慣れていない私たちだったのでSwaggerUIのようなものがあるだけでもAPIのイメージのしやすさが全然違った）
    - SQLAlchemy
        - DB操作のORMとして使用。pydanticと合わせてスキーマの定義からクエリの作成、実行を効率化。
- 非採用のもの
    - Redis
        - リアルタイムな状態管理に使用され、本アプリではクイズの進捗状況の管理に使うか検討。普通にRDBMSで十分と判断して採用見送り
    - WebSocket
        - 同上。学習コストが高そうだったため採用見送り（雑魚ですみません...)
### AWS構成
今回ほぼ初めてAWSを触りました。(どれぐらいのレベルかというと、３か月前はEC2でgitが使えることすら知りませんでした。先にコードを書いてローカルで動くことは確かめていて、全世界に公開したかっただけなのに、ネットの記事を見てもまずAWSでインスタタンス立ち上げてそこでコードを書いていくものが大半で困っていたのは秘密。)そのため、意図せず悪意なく構成に関しては嘘をついている可能性があります（私としては以下のイメージでつくりました）。この書き方、構築は素人丸出しでありえないぜ！のようなコメントは広く歓迎します。

![AWSflow2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705827/04361883-075c-894f-e656-d1556ebab8ba.png)

EC2インスタンス一つでfront, back両方をホストして、DBにはSQLiteを使っています。EC2インスタンスを一つしか用いていないのにELBが入っていて気持ち悪く感じる人がいるかと思いますが、元々次のような構成にするつもりで、その名残です。

![AWSflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705827/68ab7ef1-e4f0-7f3b-72e8-1b04f7f31ae7.png)

もっと言うと最初はAmplify+Lambdaで作ろうと思っていました。しかしAmplifyは機能がリッチすぎて「何でできたかわからないけどとりあえずできていてすごい～」となっていたため使用をやめました。大してAPIも叩かれないし余分なコストは削ろうと考えていたら最初の構築になりました。
今の時代CI/CDは当たり前という気持ちでいたのですが、優先度は低いと思って放置していました。最後まで諸々を手動で行うことになり、本当に反省しています。


# 開発プロセス
時系列で書いていきたかったのですが、あまり覚えていないため印象的だったことを書いていきます。
### 企画段階
企画段階では、ゲームの「面白さ」を確認することから始まりました。デザインの世界でいう「ジャーニーマップ」をつくる感覚です。最初は、同期でLINEを使って簡単なクイズを試してみました。
試して分かったことは、以下の2点です。
- ユニークな回答だと思っていたのに他の誰かと被った時の盛り上がり
- 誰とも被らない回答ができたときの喜び、達成感

これらの経験から「結果発表時のUIが非常に重要」と考えました。またユーザーが気にするのは以下の点だろうと考えました。
- 自分と同じ回答をしたのは誰なのか
- どの回答が多かったのか、少なかったのか

逆に、具体的な得点のようなものはあまり気にならないといったことも分かりました。

### UXの追求
この気づきを活かし、UX向上のために次のように方針になりました。
- 結果発表を楽しめるように、GoogleFormのような既存のものではなく自分たちで開発する必要がある
- 回答は「プルダウン」ではなくvalidation付きの「自由記述」にする

（私が書いていますがこれは主に他開発メンバーが出した考えです。鋭い...!!）当日の様子からは、自由記述形式が参加者の創造性を引き出し、独創的な回答を考える楽しさを生んでいることが見て取れました。その結果、予想を上回る盛り上がりとなりました。
### システム全体像の整理
次のステップとして、システムの全体像を整理することに取り掛かりました。私個人としては画面作りから始めようとしていたのですが、先輩から一言：
> まず業務フローの整備では?

この一言を受け、まずFigjamで流れを整備することになりました。これにより以下を明確化しました（結局、開発途中でだいぶ変わりました）
- 必要な画面とその画面遷移
- APIの設計

これにより開発の方向性が明確になり、front, backそれぞれ目線を揃えた状態で個別に開発を進めることができるようになりました。いよいよ開発のスタートです。以下は実際の作業時の様子です、レビューもないため楽しく作業しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705827/6194948f-a43c-077e-5cd5-c99341eb62c2.png)
業務フローの整理。猫がかわいい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705827/e0131de3-f53b-cc7b-19d4-7f8b6837fbbd.png)
figjamでワイワイ作業をしている様子

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705827/326709c6-8d35-bb63-009b-b5311eb0702e.png)
DBの属性の定義もおそらくこのタイミングで（もっと後だったかもしれません）

### 技術選定
技術選定は私に一任されていたため、chatGPT君を壁打ち相手にして進めました。新人研修で触ったjQueryやSpringは「企業システム」感が強く、どうも面白みに欠けたため、より現代的な技術スタックを検討しました。（基本的に上の項参照）
pythonのパッケージマネージャーとしてuvが魅力的でしたが、使い慣れているpipを選択するなど（gitに慣れていない人もいるときに色々手を出すべきではないので）、現実的に良い落としどころを見つけつつ進めていったように記憶しています。

### アジャイル開発の壁
アジャイル開発に挑戦しようとしましたが、React・FastAPI・AWSといずれもに初めて触る技術だったため開発速度（velocity）の予測が難しい状態でした。また他の案件と並行していたこともあり、理想的なスクラム開発とはいかなかった気がします。（スプリントなしアジャイルorドキュメントなしウォーターフォールをやっていた気がします）（開発に専念させられるスクラムマスター大事！）チームの熱意でなんとか最後まで進め切ったなという印象です。
### 技術的な話
技術面では、フロントエンドからのAPI呼び出しの方法が分からないといった基本的なことから(axiosで叩くだけ)、AWS RDSやLambdaとの格闘の末、シンプルにEC2＋SQLiteを採用するなど、実用的な（忘年会余興という目的に即して）判断を重ねた気がします。userとAnswerという2つのDBに関してCRUDを実装することは容易でしたが、その後にログイン情報をどこで持つのか（そもそも必要なのか）、クイズ全体の進行をどのように制御するか（どのように回答を締切り、集計結果を見せるか）は苦戦した記憶があります。
また、回答結果の表示、得点の集計/計算方法もなかなか時間をかけました。回答の表示では1次元的な表示にするのか2次元、3次元的な表示にするのか、どこまでの情報をユーザーに見せて何は見せないのか等、だいぶ話し合った気がします。また得点の集計においても単純に被った人数で割る案や、情報量（エントロピー）に応じてスコアを付与する案、回答をembeddingしてコサイン類似度を取り射影して距離に応じてスコアを付与などの案がありました。最終的には最初に見せた画面イメージのようにシンプルな見せ方、集計方法に落ち着きましたが、もしかしたらここはまだまだ改善の余地があるのかも...と個人的には思っています。
最後に負荷対策として、Locustを使用して負荷テストを実施しました。実際の使用シーンを想定し、全体進行や回答締切り間際に集中する負荷を再現しました。10秒のうち特定の1秒に4000クエリが集中するようなシナリオでテストを行い、システムの耐久性を確認しました。t2microだと落ちたため、t2mediumに変えたりしました。Locustも初めてで本当に負荷テストができているか不安だったため、実際に200窓開いて操作したりもしました（頭悪い）。
![locust.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705827/3864d97f-82f6-a13a-bd92-92938fe40a01.png)
負荷テストの様子。レスポンス時間の95%タイルが2.5secとやや長いが、実際はこんなにアクセスが集中しない、したとしても忘年会で酔っている最中の2.5secなら我慢できると判断。


# 忘年会当日：アプリの反応
忘年会当日は開発メンバー2人で前に立ち、スライドを映しながら参加者の回答に対してコメントを加える形で進行しました。開発者として、予期せぬ操作（大量のブラウザバッグ、連続リロード、不正なコマンド入力など）によってサーバーがダウンしたり、アプリケーションが異常な動作をしたりしないかと、終始緊張感をもってモニタニングをしながら司会進行していました。（なんといっても24時間365日動いている製鉄所のシステムを誇る弊社ですから）
結果的には、一部トランザクション処理に改善の余地は残るものの、大きな問題も発生せず、イベントを無事に終えることができました。参加者の皆さんが楽しそうに取り組んでいる様子を見ることができ、満足しています。

# 学びと振り返り
技術的な学びとしてはReact, FastAPI, AWSなど現代的な技術スタックに触れるいい機会となりました。特にAWSについては壊しながらも少しずつ学び、理解できる範囲でうまく構成できたと思っています。ユーザーを想定した画面設計や、Locustを使用した負荷テストの実施など、「本番ぽい」実践的な経験を得ることができました。
開発メンバーと何度かラーメンを食べに行くなど、しっかりチームとしても成長もできたように感じています。個人的にチーム開発に対しては苦い思い出もある中で、とりあえず余興として披露し、ユーザーに触ってもらい、良いフィードバックを得られたということで、一安心しています。

# 次の挑戦
本アプリケーションの発展可能性として、以下のような展開が考えられます（やるとは言っていない）：
- 社内イベントプラットフォームとしての展開
    - 社内でのイベント、研修時などで多くの人と購入する際に、打ち解ける機会を提供できると考えます。
- 機能拡張について
    - より柔軟な回答形式（画像など）の追加
    - 問題の追加/削除、部屋立て/削除など

３か月近く開発をしてきて、余興のわずか30分だけ使って終わりというのは寂しい（それこそコスパが悪いように感じる）ため何かやりたい気もしますが、ここからぐんぐん進めていくだけの推進力～～といった状態です。

# おわりに
本開発を通じて、技術的なスキルアップだけでなく、実際のユーザーに使ってもらうアプリケーションをつくる難しさと面白さの一端に触れられました。AIによって開発が加速する昨今、「何を目的に何をつくるか」に関して考えるいい機会になりました。誘ってくれた開発メンバー、テストプレイ（デバッグ）をしてくれた同期・先輩の方々、忘年会当日楽しく遊んでいただいた皆様、本当にありがとうございます。
最後に、記事を読んでいただいた方々、特に実装や運用に関して気になる点がございましたら、ぜひコメントでご指摘いただけますと幸いです。特にAWS構成については素人がつくったハリボテなため、より良い構成案などございましたらご教示ください。
最後まで読んでいただきありがとうございました！