title | emoji | type | topics | published | ||||
---|---|---|---|---|---|---|---|---|
オンライン自習室アプリを個人で開発した件 |
📖 |
tech |
|
true |
私は、「アプレンティス」の2期生として、現時点で約6ヶ月間、プログラミングの学習をしています。
そのカリキュラムの中で、「Sabo Learn(サボラーン)」というオンライン自習室を提供するWebアプリをリリースしました。
前置きとして、開発期間は約2ヶ月間です。(平日は仕事をしているため、実稼働はもっと少なく、約500時間が開発に使える時間でした。) また、「誰かの課題を解決する」というテーマで、実際に使ってもらえるプロダクトを目指して開発しておりますが、それと同時に、自身の転職活動のポートフォリオでもあります。
そのため、随所に、「どうしてわざわざそんな技術を使ったの?」「インフラのスペック過剰じゃない?」といったツッコミどころがあるかもしれませんが、そのほとんどは「自分が知らない技術をキャッチアップしたかったから」か、「これくらいのことなら実装できます」というアピールのためです。
このような前提があることを踏まえ、以下を読み進めて頂ければ幸いです。
課題を解決できるプロダクトであることはもちろん、ポートフォリオでもあるということを考えると、
- 2ヶ月でユーザーが実際に使用でき、ユーザーに価値を提供できる
- ポートフォリオとして最低限の技術をアピールできる内容
ここを押さえておく必要があります。特に2ヶ月でデプロイまで終わっていることはマストです。
例えば、設計の段階できっちり決めていたとしても、開発やデプロイに手間取ってしまい、結果プロダクトとして完成できなかったのでは意味がありません。
そこで、以下のような計画を立てました。
- プロダクトとして機能する最小構成まで開発を進める
- インフラの構築とデプロイ
- その後、時間が許す限り機能を追加していく
ある程度のところまで完成したら、まずデプロイすることにしました。さらに言えば、CI/CDまでできていれば、その後の機能追加はスムーズにできるからです。
- 学生、社会人などテストや資格取得に向けて勉強しなければならない人。
- 自宅で1人で勉強をしていても、ついサボってしまう。
- カフェやコワーキングスペースなどは、お金がかかるので毎日通うのは難しい。
このような方々に、「サボらない自習環境」を提供するアプリです。
リンク: Sabo Learn(サボラーン)
開発する前に、以下のような項目について考えをまとめました。
- 誰のどんな課題を解決するのか?
- なぜそれを解決したいのか?
- どうやって解決するのか?
- 競合調査
::: details プロダクトテーマ
勉強をついサボってしまうあなたへ、サボらないための環境を提供します。
- 学生、社会人などテストや資格取得に向けて勉強しなければならない人。
- 自宅で1人で勉強をしていても、ついサボってしまう。
- カフェやコワーキングスペースなどは、お金がかかるので毎日通うのは難しい。
日本の勉強に対する意識について調査を行った。
その結果、日本人の平均学習時間は世界最低レベルであることが分かった。そこで「勉強しなければならないがそれができない人」に対して、勉強を継続できる場を与え、個人の学習時間を少しでも増やし、日本人の学習量を増やしたい。
- 勉強しなければいけないのに面倒くさいと感じてしまう。
- 勉強を始めてもいつの間にか集中が途切れてしまいスマホをイジっている。
- 面倒くさい以上に勉強をするのが楽しみ
- 勉強しているときにサボることがなくなる
- 面倒くさいことを減らす
- 面倒くさい以上の楽しみを作る
- 集中できない原因を取り除く
- 集中できる環境を作る
- 画面構成をシンプルに
- ローディングは極力減らす
- 簡単に環境を用意できる
- 勉強するほどポイントがもらえる
- 共有したくなるような画面をつくる(勉強している自分イケてる)
- 連続ログインボーナス
- フレンドと勉強
- ランキング機能
- 目標を掲げる
- スマホと連携する機能(連携しているとポイント獲得率UP)
- モーションキャプチャでサボっていたら通知
- サボっている人を注意できる
- 他人と同じ環境に置く(人が頑張っているから自分も頑張ろうという気持ち)
- 音楽再生機能(BGM、環境音など邪魔にならないものだけ)
-
MyroomNeo 専用のZoomに招待する。手元のみを映す。見られている環境で集中することはできる。 常時ではないが管理人が監視している。
デメリット → 有料である。手元とはいえカメラに移さなければならない。
-
Herazika モザイクでプライバシーが保護されている。
デメリット → 有料である。現在期間限定で500円/月(本来は3000円/月)1コマ25分で区切られているため、連続して入室ができない。事前に予約が必要。
-
StudyCast(スタキャス) ベースは学習記録管理アプリ。オンライン自習室では、友達と一緒に勉強できる。
デメリット → ターゲットは中高生であり、社会人向けではない。
:::
要件定義では、現状の課題と、それを解決する方法を以下のように考えました。
:::details 要件定義
- 勉強しなければいけないのに面倒くさいと感じてしまう。
- 勉強を始めてもいつの間にか集中が途切れてしまいスマホをイジっている。
- 面倒くさい以上に勉強をするのが楽しみ
- 勉強しているときにサボることがなくなる
- 面倒くさいことを減らす
- 面倒くさい以上の楽しみを作る
- 集中できない原因を取り除く
- 集中できる環境を作る
- 画面構成をシンプルに
- ローディングは極力減らす
- 簡単に環境を用意できる
- 勉強するほどポイントがもらえる
- 共有したくなるような画面をつくる(勉強している自分イケてる)
- 連続ログインボーナス
- 学習記録をグラフ化
- フレンドと勉強
- ランキング機能
- 目標を掲げる
- スマホと連携する機能(連携しているとポイント獲得率UP)
- モーションキャプチャでサボっていたら通知
- サボっている人を注意できる
- 他人と同じ環境に置く(人が頑張っているから自分も頑張ろうという気持ち)
- 音楽再生機能(BGM、環境音など邪魔にならないものだけ)
()内の数字は「具体的な解決策」に対応
- ログイン機能
- オンライン自習室機能(4-1)
- ポイント付与機能(2-1)
- サボっている人に対して注意できる機能(3-3)
- 音楽再生機能(4-2)
- 学習時間グラフ化(2-4)
- SPAの構成で直感的に操作できる(1-1)
- 通常のページ移動は1秒以内(1-2)
- マッチングは15秒以内(1-2)
:::
デザインを作成するまえに、大まかな画面のレイアウトと、遷移図も兼ねたワイヤーフレームを作成しました。
ターゲットユーザーは大学生、社会人であるため、大人っぽいデザインを意識し、黒を貴重としたUIにしました。文字を主要なコンテンツとする場合、背景色は白である方が読みやすいのですが、今回はそうではないため、UX的にも問題ないと思い思い切って黒を選択しました。
- Backend: Rails ( API mode / Rspec / rubocop ) + Nginx
- Frontend: Next.js ( eslint / prettier )
- Socket Server: Node.js, socket.io
- Infra: AWS ( Amplify / ECS Fargate / ECR / RDS / ALB / Route53 ), Docker & docker-compose
主要gem
jwt
: jwt認証を実装するため。発行したtokenをfront側へ送りcookieにセット。このtokenをbackend側で検証することで、ログイン状態を判定。rubocop
: Rubyの静的コード解析。rspec-rails
: テスト用フレームワーク。factory_bot_rails
: テスト用のダミーデータを作成。database_cleaner
: テスト実行時にデータベースを初期化。pry-rails
: デバッグツール。
:::message
2024.5.22追記
当初、テスト前にmusicとwallpaperが初期データとして必要であったため、before(:suite)
としてデータを作成していました。
この場合、config.use_transactional_fixtures = true
にしていても、テスト実行後にデータが残ってしまっており、そのためdatabase_cleaner
を導入したという経緯があります。しかし、before(:each)
にすることで、テスト後にデータを消すことができました。
さらに、database_cleanerをやめたことで、テストの実行速度が格段に早くなったので、本当に必要かどうか検証して導入すべきだったと学びました。
:::
主要ライブラリ
ress
: リセットCSS。sass
: cssをscssで記述するため。dnd-kit
: ドラッグ&ドロップでの並び替え機能を実装するため。react-countup
: カウントアップアニメーションを簡単に実装できる。socket.io-client
: socket.ioを利用するため。
主要ライブラリ
nodemon
: コードの変更時に自動でサーバーを再起動してくれる。socket.io
: サーバーとクライアントの間のリアルタイム通信を行うため。WebRTCでsdp/candidateを交換する目的で導入。
AWSを中心に以下の図に示したインフラを構築しました。
Next.jsを使った開発のため、Vercelへのデプロイも考えましたが20ドル/月かかります。ユーザー数が少ないうちはAWSを使う方が料金を抑えられるためAWSのサービスを使用しました。Cloudfront + S3を自分で構築する方法もありますが、実装期間が限られていることもあり、簡便にgithubと連携してプルリクベースでデプロイもできるAmplifyを選択しました。
https通信を行うため、SSL/TLS証明書を管理しています。
お名前.comで取得したドメインをルーティングさせています。フロント用とバックエンド用にサブドメインを設定しました。
障害時やオートスケール時の入力トラフィックを適切なコンテナに分散させています。
Dockerベースでの開発を行ったため、ECRを採用しました。ECSを選択することで、EC2に比べてインフラの管理がラクになるのが最大のメリットです。もう一つの採用理由はECSを使った構成をやったことがないためというのがあります。運用コストのことを考えると、Fargateを採用することで、パフォーマンスに対するコンピューティングコストは、ECS on EC2に比べると割高になってしまうため、今後の検討が必要ではある部分だと考えています。
CloudWatchで、Rails、Socket Server、RDS、AmplifyそれぞれにCPUの使用率や、HTTPステータスエラーに対してアラートを設定し、SNS、ChatBotを経由してSlackへ通知されるようにしました。
github
: ソースコードの管理。issueベースでブランチを切って開発しました。github Actions
: CI/CDを実装。Docker/docker-compose
: 開発環境(front, backend, SocketServer, MySql)を全てDockerコンテナで構築。backendは、AWS ECS(Fargate)へのコンテナデプロイのため、サーバー構築が不要で、拡張にも対応できる。Postman
: APIへのテストに使用。Sentry
: フロント(Next.js)のエラー監視。
テーブル名 | 役割 |
---|---|
users | ユーザー情報を管理 |
musics | 部屋のBGMである音楽 |
music_purchases | ユーザーが購入した音楽を管理する中間テーブル |
wallpapers | 部屋の背景画像 |
wallpaper_purchases | ユーザーが購入した壁紙を管理する中間テーブル |
playlist | ユーザーが作成したプレイリスト |
playlist_musics | プレイリストに登録されている音楽を管理する中間テーブル |
そこまで、複雑ではないかと思いますが、正規化として、users - musics、users - wallpapers、playlist - musicsはそれぞれ多対多の関係にあるため、中間テーブルを作成しました。
- googleアカウントを利用したユーザー登録(OAuth認証)
- 滞在時間に応じてコインが付与される
- オーディオプレイヤーで音楽を再生できる
- 動体検知カメラによりユーザーのサボりを判定(サボり判定中は時間が止まる)
- (マルチ部屋の場合)最大4人までのビデオ映像が画面に表示される
- 獲得したコインを消費して、背景画像、音楽を購入することができる
- ソロ部屋、マルチ部屋それぞれに、購入した背景画像を設定することができる
- 購入した音楽からプレイリストを作成し、オーディオプレイヤーで再生できる
- プレイリストはドラッグ&ドロップで作成。曲順も設定可能
- スマホ(縦横両方)、タブレットへのレスポンシブ対応
- フロント側のログイン機能はNext Authで実装
- ログインのjwt tokenを、cookieとしてHttp Only属性と、Secure属性を付与して保存
- APIへのリクエストを行うfetch関数は、すべてサーバー側で行うため、リクエスト情報は公開されない
- Socket Server、RailsにはCORS設定を行い、アクセスできるオリジンを制限
- AWSでは、ECS、RDS、ALBそれぞれセキュリティグループを設定し、必要なアクセスのみを許可
- ビデオカメラにはモザイク処理をかけてプライバシーに配慮。WebRTCに乗せるビデオは、モザイク化したビデオを一度canvas化し、再度ビデオに変換することで、モザイク除去できない仕組み
- 開発環境はDockerによりコンテナ化
- Socket Serverを使用してリアルタイムにsdp / candidateを交換
- github actionsで自動テスト実行(Rspec、rubocop, ESLint)
- frontendは、Amplifyによる自動デプロイ
- backend, socket serverは、github actionsで自動デプロイ(それぞれのディレクトリで差分を検知したとき)
- CloudWatchでエラーを監視し、Slackへ通知
- Google Analyticsでユーザー分析
現在の機能では、まだまだ機能不足だと考えています。 そこで、現状に満足せず、さらにサボりにくいサービスとなるよう、以下のような機能を考えました。
-
追加機能
- ホワイトノイズ、環境音、雑音などのBGMを追加
- SHOPにソート機能、タグ検索を追加
- ビデオのフレームを購入
- 休憩室機能
- ポモドーロタイマー
- フレンド機能
- ランキング機能
- 学習時間を記録しグラフ化
- 他人の学習時間を見える化
- ビデオではなくバーチャルキャラ化 + アバター購入
- 目標設定機能(他人の目標も見られる)
-
マッチングにロジック追加
- ユーザータイプを診断し、それに応じてマッチング
-
ボーナスコイン
- 朝型夜型診断とボーナスコイン
- スマホ連携によりコイン獲得量アップ
- 勉強時間に応じてボーナスコイン獲得
- マルチルームは人数によってボーナスコイン
「勉強するなら、誰かと一緒に勉強できると楽しそう」という安易な考えから、ビデオを繋げる機能を実装することにしました。
そのためには、「WebRTC」という技術を使うみたいだというところから、「P2P通信」「SDP」「candidate」「シグナリングサーバー」「STUN/TURNサーバー」と、調べれば調べるほど、知らない単語がたくさんでてきて、正直諦めかけましたが、ひとつずつ知識を増やしながら進めていくことで、実装することができました。
備忘録として、別の記事にもまとめています。
https://zenn.dev/makoto00000/articles/2665d90fe95f3a
フロントエンドのデプロイ先として、Next.jsを使用していたため、Vercelを考えましたが、料金的なところや、学習として自分が使ったことのないものを使ってみようという観点から、AWSのAmplifyを選択しました。
こちらも、ディレクトリを以下のように全てまとめて管理していたことが原因で、デプロイでつまづきました。
app
┣ frontend
┗ backend
試行錯誤して、ようやくデプロイにこぎつけたので、こちらも別記事に詳細をまとめています。
https://zenn.dev/makoto00000/articles/4cbfe554ff8837
これまで学習として、EC2へのデプロイは経験していたので、こちらも新しいことへの挑戦という意味で、ECR/ECSへのデプロイに挑戦してみました。
セキュリティグループの設定だったり、環境変数の設定だったり、いろんなところでつまづきました。
そのときのツイートです↓
その後、ようやく手動でデプロイすることに成功しました。
そのときのツイートです↓
最終的には、github actionsを使った自動デプロイまで、実装することができました。
今回のプロダクトの開発を通して、ひとつ重要なことを学びました。
それは、諦めなければ何でもできるということです。
私の嫌いな精神論ですが、事実「WebRTC」も「AWS」も最初はこんなものできるわけないと挑戦しなければ、何も生まれなかったわけで、諦めずにやってみたから実際に「できた」わけです。
思い返せば、自転車に初めて乗ったとき、車のハンドルを初めて握ったとき、やっぱり「こんなものできるわけない」と思ったものの、結局今では当たり前にできています。
人間が成長するときって、いつもこうなんだなと改めて思いました。
最初にも申し上げましたが、私は社会人として平日は仕事をしながら、帰って来てからの時間と、休日を使って、独学でプログラミングを学習してここまで来れました。
IT関連の業種ではなく、医療職でしたので、もちろん前提知識が何もないところからのスタートです。
もしこの記事が、私のように未経験から勉強しているような方の励みになれば幸いです。
最後にこのプロダクトのURLをもう一度貼っておきます。
少しでも興味を持って頂けましたら、覗いてみてください。
デプロイ後にユーザーに実際に使用して頂き、インタビューを実施しました。
その中で「使い方がわからない」という意見を多く頂きました。
そこで新規登録者に対して、コーチマーク(チュートリアルみたいなもの)を表示するアップデートを実装しました。
また、自習室へ移動するときのローディング画面を利用して、カルーセルで簡単な使い方の説明を追加しました。
また、今後もユーザーの反応を見ながら、より使いやすいアプリを目指してアップデートを継続していきます。