オンラインで○×クイズが遊べる「○×クイズONLINE」をリリースしました
○×クイズONLINE
サービスURL
○×クイズオンライン というサービスは、オンラインで○×クイズを行いたいエンジニアイベント参加者向けのパーティーゲームです。 ユーザーは ブラウザ上で自分のアバターを○のエリア、×のエリアに移動することによって二択クイズの回答をすることができ、チャットで ○ or × と発言するのとは違って、誰が、どれくらいの人数の人が、 ○ or × と答えているか、また回答に迷っている様子がリアルタイムで見れる機能 が備わっている事が特徴です。
- ○×クイズONLINE
- 使用技術
- 簡単な自己紹介
- このサービスを作った経緯
- このサービスの使い方
- どうやってwebでリアルタイム通信を行っているか
- 大変だったこと
- 自作サービスを作る上でやってよかったこと
- このサービスのこれから
- わかりやすかった参考資料など
- さいごに
特徴
アバターの動きをリアルタイムに反映
自分のブラウザで自分のアバターを動かすと、他の参加者全員のブラウザにその動きがリアルタイムに反映されます。同時に、他の参加者のアバターの動きも自分のブラウザにリアルタイムに反映されます。
チャット機能
LINE風のチャットも使えます。もし不要の場合はOFFにできます。
こんな時に使えます
オンラインイベントを盛り上げたい
オンラインでの勉強会やミートアップなど、イベントの余興として最適です。新規参加者もゲームに参加しやすく、和気あいあいと楽しめます。
新入社員歓迎会
新入社員歓迎会などにもご利用いただけます。その会社にまつわるクイズや、一般常識を問うクイズなどを作成すると盛り上がるかもしれません。
オンライン飲み会
仲間と話しながらお酒を飲むだけでも楽しいですが、マルバツクイズで遊ぶと更に楽しくなります。ルールも簡単で誰でもわかるので気軽に遊べます。
使用技術
バックエンド
- Ruby 3.2.2
- Ruby on Rails 7.0.5
フロントエンド
- Nuxt.js 3.5.2
- TypeScript
- Tailwind CSS
- daisyUI
インフラ
環境構築
- Docker
- Docker Compose
テスト
- RSpec
- Vitest
- Cypress
Linter/Formatter
- ESLint
- Prettier
- rubocop
CI/CD
- GitHub Actions
外部サービス
簡単な自己紹介
今年で34歳になる福岡生まれ福岡育ちのナカムラといいます。2023年現在、未経験でwebエンジニアになるべく学習&転職活動中です。前職は郵便屋さんでした。
ある日、山道の郵便配達中に急に制御不能になったスーパーカブでフェンスを突き破って崖から落ちて死にかけたことがきっかけでキャリアチェンジを決意しました。
キャリアチェンジの方向性としていくつか候補をあげ、その中の1つがエンジニアでした。プログラミングのプの字も知らない状態からとりあえずProgateをやってみたところこれがとても楽しく(もっと早くに出会っていれば・・・!)、本格的に学習をするためFJORD BOOT CAMPに入隊しました。
このサービスを作った経緯
私が所属するFJORD BOOT CAMPでは月に1度、オンラインでミートアップを行います。日本全国の受講生がオンライン上に集まり、ビデオ通話でアルコール片手に親睦を深める会です。
FJORD BOOT CAMPではこのようなオンラインイベントを定期的に行っているのですが、イベントを盛り上げたり、各参加者同士の親睦を深めるようなパーティーゲーム、特に「リアルタイムで皆の様子がわかるような○×クイズ」が欲しいという要望がありました。とても作りがいがありそうな要件&受託開発に興味があるという理由から開発に手を挙げさせてもらいました。
このサービスの使い方
クイズの出題者として
1. ゲームを作る
ホームの「新しいゲームを作成する」からゲームを作成します。ここでクイズを複数問登録します。
2. 会場URLをコピーする
ゲームの作成が完了したら、そのゲームの会場URLが発行されるのでそれをコピーします。
3. ゲーム会場にアクセス
ゲームの作者が一番最初にゲーム会場にアクセスしてください。アクセス方法は、ゲーム詳細ページの「ゲーム会場へ入る」をクリックしてください。作者が会場に入っていないと、他の参加者が会場に入れないので注意してください。
4. 会場URLを参加者にシェア
ゲーム作成者がゲーム会場URLにアクセスした状態で、ゲームの参加予定者に対して発行されたURLをシェアします。推奨参加人数は10人前後を想定しています。
5. 参加者がゲーム会場にアクセスするのを待つ
参加者全員がシェアしたURLにアクセスするのを待ちます。アクセスした人は「入室済み」となります。
6. 参加者全員が会場に入ったことを確認する
ゲーム作成者には、どの参加者がゲーム会場にアクセスしてきたかわかるようになっています。全参加者が入室したことを確認できたら、「ゲームを開始する」をクリックします。クリックすると接続が始まるので接続が終わるまで待ちます。
7. ゲームを開始する
接続が完了するとクイズが出題できるようになります。画面右上にゲーム作者用のメニューがあるのでそこから出題します。「n問目を出題する」というボタンを一度クリックすると、出題、回答、判定、解説まで自動で行われます。解説まで終わると次のクイズを出題できるようになります。
クイズの回答者として
1. シェアされたURLにアクセスする
出題者からシェアされたURLにアクセスします。
2. 接続が完了するまで待機
アクセスできたら、ゲームが開始されるまで待機します。
3. アバターを動かして回答する
全ての参加者が接続を完了したらゲームが開始できるようになります。出題者からクイズが出題されたらアバターを動かして回答しましょう!
どうやってwebでリアルタイム通信を行っているか
WebRTC
WebRTCというwebプラットフォームでリアルタイム通信を実現するためのプロジェクトがあります。 WebRTCとはwebでリアルタイム通信を行うための様々な技術の集合体で、APIとしてブラウザに実装されており、新しいプラグインなどを追加せず標準で利用できます。WebRTCの一部としてP2P通信が採用されており、今回はこのP2Pという通信技術を利用し、各ゲーム参加者同士のブラウザを相互接続してリアルタイムにデータを送受信することでDOMを同期しています。
P2P
通常webで利用される通信は主にクライアント・サーバー方式となっています。webサイト利用者は各自ブラウザからサーバーに向けてリクエストを送り、返却されたHTMLなどを受け取ります。 P2P通信ではクライアント同士が相互に接続します。完全にクライアント同士が独立して相互接続を行うP2PをピュアP2Pと言い、サーバーを介してクライアント同士が相互通信するP2PをハイブリッドP2Pと言います。今回はサーバーを介して相互接続するハイブリッドP2Pを利用しました。
SkyWay
ただ、このハイブリッドP2P通信を実現するためにはいくつかのサーバーが必要になってきます。通信相手のIPアドレスなどの情報を知るためのシグナリングサーバーや、NAT越えの要否を判断するSTUNサーバー、実際にNAT越えを行うTURNサーバーなどです。もし完全に自作でwebアプリにP2Pを実装する場合上記のサーバーを自身で建てなければなりません。そこでそのような各種サーバーを用意してくれたり、WebRTCをよりわかりやすく使うためのJavascriptSDKを提供してくれるSkyWayというサービスを利用させてもらいました。
このサービスはweb用のJavascriptSDKだけではなくiOS用やAndroid用のSDKも用意されており、簡単に自身のアプリにビデオ通話機能などを組み込めます。主な想定利用シーンはビデオ通話などのメディアチャネルを通じたリアルタイムコミュニケーション用として提供されていますが、テキストデータなどを送受信するためのデータチャネルも提供されており、今回はこのデータチャネルを利用しアバターの座標データなどをブラウザ間で送受信し、DOMに反映させています。
大変だったこと
1枚のHTMLに多面的な視点で処理を詰め込む
webアプリで複数人が同時接続するリアルタイムなゲームを実装するにはHTML1枚の中にかなり多くの処理を詰め込まないといけません。処理の多さだけでなく、「データを送信する側の処理」と「データを受信する側の処理」といった視点や、「出題者として」、「回答者として」の視点など、多面的な視点を考えながら1枚のHTMLに処理を書いていく必要があります。 また、全参加者同士のブラウザを相互接続する際に、一度に全員が相互接続の処理を開始してしまうとSkyWayのサーバーがパンクしてしまいます。SkyWayではこのような多くのクライアントが同時に接続した場合、SFUサーバを使用してサーバがパンクすることを回避する仕組みが用意されているのですが、それはメディアチャネルのみでデータチャネルはサポート外となっています。なので自力でSkyWayのサーバがパンクしないように各クライアントが相互接続する順番などをハンドリングしていく必要がありました。
このような背景から、コードの保守性や、再利用性、理解しやすさなどは一旦横において「とりあえず動く」という状態のものが出来上がった時、1枚のHTMLに書いたjavascriptの行数が1000行程になり、とんでもない魔境が爆誕してしまいました。どこに何が書いてあるか探すのが大変ですべて投げ出したくなる衝動に何度も襲われました。
ですが諦めず、HTMLをコンポーネントに細分化していき、Javascriptもクラスを分けて何度も整理していく内にかなり見通しが良くなりました。以前は、オブジェクト指向についてフィヨルドで学習していましたが、正直「オブジェクト指向はなぜ重要なのか」ということを実感していませんでした。今回、クラスを利用して整理していく中で、オブジェクト指向の便利さを身にしみて実感しました。
本番環境の動作確認が1人でできない
「複数人で同時に遠隔地からアクセスする」という実際のシチュエーションを再現するのは無理がありました。なのでFJORD BOOT CAMPでは毎週水曜日、自作サービスの進捗を報告するミーティングがあるのですが、そこで他の受講生の方に協力して頂いていました。皆さん積極的に協力いただいてとても助かりました。完全に一人ではこのサービスは開発できなかったと思います。FJORD BOOT CAMPの皆様本当にありがとうございました。
使ってみたい技術を詰め込みすぎて新規学習コストかさんだ
自作サービスの技術構成に関して、「興味がある技術を取り入れたい」、「この際苦手を克服したい」という思いから、フィヨルドのプラクティスには無い技術を多く採用しました(Docker, Typescript, Nuxt, Firebase, Rspec, Vitest, Cypressなど・・・)。特に、認証機能にFirebaseAuthenticationを利用したのですがこれがとても難解で理解するのに時間がかかりました。
実際に開発している時は学習コストが増えすぎて結構しんどかったです。ですがその代わり、自分の技術の引き出しは以前に比べて大幅に増えたと思いますし、未知の技術に対する耐性もできてよかったです。
自作サービスを作る上でやってよかったこと
壊れてもいい練習用リポジトリを用意する
まったく初めて0からサービスを作った経験がなく手探り状態だったので、とりあえずAPIにHello
をリクエストし、レスポンスを表示するだけのフロントエンドと、Hello
を返すだけのAPIをつくりました。本命のアプリに似た、壊れても大丈夫な練習台を用意しておくと特にインフラ周りを色々試せるので良かったです。
サービスの最重要機能から実装に取り掛かる
まずサービスの最重要機能から取り掛かった点は良かったと思います。今回のアプリのメインの機能は「リアルタイムにブラウザ同士でデータをやり取りする」という部分だったので、その機能が「そもそも実際に実現できるのか」ということを一番最初に確定することで安心感を得られました。もし例えば認証機能やリソースのCRUDなどから実装を始め、アプリの外堀を固めた上でメイン機能の実装にとりかかった場合、それがもし実現できなかった、そもそも不可能だったとき、下手したら今までの作業が全て無駄になってしまいます。なのでまずは最低限のプロトタイプを作成し、本番環境にデプロイまで行いました。このプロトタイプで動作確認を行いリアルタイム通信ができると確信した上で、その他の機能を追加していきました。
早い段階でCI/CDを構築する
早い段階でCI/CDを構築したことも良かったと思います。CI環境は自分が思っているより環境の違いに悩む場面が多いです。ローカルではしっかりテストは動くのに、いざCIでテストを実行すると動かないor何故かテストが落ちたりします。また、ローカルでは動くのに本番環境ではアプリが動かないといったトラブルが起きます。初期段階でこの辺りを構築しておくと、問題が発生した時に原因の特定が比較的容易になると思います。
このサービスのこれから
クイズ会場のUIをもっと洗練させたい
今はPC画面を模したデザインになっていますが、今後はもっと洗練させてよりゲームっぽいUIにしていけたらと思っています。また現在はクイズ会場のUIは1択しかありませんが、この選択肢を増やす、ユーザーがアップロードした画像を背景にするなどできればと思っています。
同時接続人数を増やす
現在は同時接続人数を10人前後で想定していますが、これを20~30人前後で同時接続して遊べるようにできたらなと思っています。
GitHubログイン以外のログイン方法を追加したい
このサービスは基本的にフィヨルドブートキャンプの関係者向けに作ってあるのでGitHubアカウントが必須となっています。これをもっとオープンに、一般の人にも使ってもらえるように間口を広げていきたいと思っています。
わかりやすかった参考資料など
WebRTC
知っている人も多いかと思いますがvoluntasという方が書かれたWebRTCに関する資料になります。日本語書かれていてとてもわかり易く、大変参考になりました。
バックエンドとフロントエンドを分ける開発方法について
こちらのwebサイトではバックエンドをRails(API)モード、フロントエンドをNuxtで構成するアプリの作り方をとても丁寧に、わかりやすく解説されてます。またDockerの具体的な使い方やJWT認証についても解説してあり、総合的にモダンなwebサービスの作成方法を学べます。自分はこちらのサイトにとてもお世話になりました。
Nuxt3
Nuxt3について詳しく解説してあります。Nuxt3はリリースされてから日が浅くNextに比べると圧倒的に参考資料が少なかったのでとてもお世話になりました。
WebAPI設計
WebAPI設計に関して、初学者にとてもわかり易く解説されてます。
OAuth
OAuthについてかなりわかりやすく解説されています。
今回作成したwebサービスでは想定利用者がフィヨルド関係者(エンジニア)なのでFirebase AuthenticationでGitHub認証を使用しました。その背後でどのような処理が行われているのかについてとても参考になりました。
Firebase Authentication
FirebaseAuthenticationでは認証にJWTを使用します。このJWTについてわかりやすく解説してあります。
Firebase Authenticationを自分のアプリに導入する際に、バックエンドやフロントエンドのやるべきことなどが図解されていてイメージしやすかったです。
フロントエンド(Nuxt)でのFirebase Authの初期化方法などが解説してあります。
バックエンド(Rails)側での JWTの認証方法などが解説してあります。
Firebase認証では、認証が成功したらFirebase側からidToken
というJWT形式(JSONをBase64URLでエンコードしたもの)のトークンが返却されます。これはFirebase上のユーザーを一意に特定するuid
やユーザーの情報(今回はGitHub Authを利用したのでGithubのユーザー名、プロフィール画像のURLなど)をJWT形式に変換したものになります。このJWTをフロントからリクエストヘッダ(Authorization)などに含んでHTTPSでバックエンドに送信し、バックエンド(今回はRails)で検証する必要があります。検証が成功するとJWTがデコードされ、ユーザー情報をペイロードから取得できます。
しかし、Firebase公式のFirebase Admin SDK にはRuby用のSDKは提供されておらず、検証において公式SDKを利用することができません。なので自分で検証用メソッドなどを自作するか、外部ライブラリを利用する必要があります。自分は以下のgemを使わせてもらいました。
一つ注意点としてこのgemでは検証のために必要なGoogleのX.509証明書を定期的にダウンロードしそれをRedisに保存し検証時に使用するためRedisが必要になります。なのでRails側からRedisに接続できるように設定する必要があったりと新たに調べることが増えて大変かもしれません。スケジュールに余裕がない人はRailsアプリの認証にFirebaseAuthenticationの採用は見送ったほうがいいかも知れません。
フロントエンド用各種ツール・フレームワーク
幅広いフロントエンドに関する各種ツール・フレームワークなどの解説をされていて、情報も新しいのでとてもお世話になりました。基本的に入門レベルなのですが、丁寧に解説されていて、記事数も多くて学びたいフロントエンドに関するおおよその概要を掴むときによく参考にさせてもらいました。
コンポーネント設計
フロントエンドをVueやReactなどで作成する場合、コンポーネント設計をすると思います。その際、「どれくらいの粒度でコンポーネントを分割するか」や「コンポーネントにどのような責務をもたせるか」など、設計の指針があった方がいいと思います。自分は当初アトミックデザインというデザインパターンに従ってコンポーネントを設計していたのですが、ルールの粒度が細かすぎて途中からしんどくなってしまいました。なので色々調べた結果、この方のデザインパターンがわかりやすく、自分にはしっくり来たので参考にさせてもらいました。
ロゴの作成
ロゴの作成にこのサービスを利用しました。とても豊富なテンプレートがあり、操作が簡単でカスタマイズしやすかったです。無料で使えます。
さいごに
自作サービスをリリースすることができてとても達成感を感じてます。
完全初心者からここまでこれたのもFJORD BOOT CAMPの方々のおかげです。特に毎週進捗報告会の際に動作確認の協力いただいた方々にとても感謝しています。
まだエンジニアとしてスタート地点にも立っていませんが、はやくエンジニアとしてのキャリアを積んでいけるように頑張っていきたいと思います。ありがとうございました!