📖

オンライン自習室アプリを個人で開発した件

2024/04/26に公開
2

はじめに

私は、「アプレンティス」の2期生として、現時点で約6ヶ月間、プログラミングの学習をしています。

https://apprentice.jp

そのカリキュラムの中で、「Sabo Learn(サボラーン)」というオンライン自習室を提供するWebアプリをリリースしました。

前置きとして、開発期間は約2ヶ月間です。(平日は仕事をしているため、実稼働はもっと少なく、約500時間が開発に使える時間でした。)
また、「誰かの課題を解決する」というテーマで、実際に使ってもらえるプロダクトを目指して開発しておりますが、それと同時に、自身の転職活動のポートフォリオでもあります。

そのため、随所に、「どうしてわざわざそんな技術を使ったの?」「インフラのスペック過剰じゃない?」といったツッコミどころがあるかもしれませんが、そのほとんどは「自分が知らない技術をキャッチアップしたかったから」か、「これくらいのことなら実装できます」というアピールのためです。

このような前提があることを踏まえ、以下を読み進めて頂ければ幸いです。

開発の進め方

課題を解決できるプロダクトであることはもちろん、ポートフォリオでもあるということを考えると、

  • 2ヶ月でユーザーが実際に使用でき、ユーザーに価値を提供できる
  • ポートフォリオとして最低限の技術をアピールできる内容

ここを押さえておく必要があります。特に2ヶ月でデプロイまで終わっていることはマストです。

例えば、設計の段階できっちり決めていたとしても、開発やデプロイに手間取ってしまい、結果プロダクトとして完成できなかったのでは意味がありません。

そこで、以下のような計画を立てました。

  1. プロダクトとして機能する最小構成まで開発を進める
  2. インフラの構築とデプロイ
  3. その後、時間が許す限り機能を追加していく

ある程度のところまで完成したら、まずデプロイすることにしました。さらに言えば、CI/CDまでできていれば、その後の機能追加はスムーズにできるからです。

プロダクト概要

  • 学生、社会人などテストや資格取得に向けて勉強しなければならない人。
  • 自宅で1人で勉強をしていても、ついサボってしまう。
  • カフェやコワーキングスペースなどは、お金がかかるので毎日通うのは難しい。

このような方々に、「サボらない自習環境」を提供するアプリです。

リンク: Sabo Learn(サボラーン)
sabolearn_eyecatch

目的

開発する前に、以下のような項目について考えをまとめました。

  • 誰のどんな課題を解決するのか?
  • なぜそれを解決したいのか?
  • どうやって解決するのか?
  • 競合調査
プロダクトテーマ

一言サービスコンセプト(サービスのキャッチコピーを一言で)

勉強をついサボってしまうあなたへ、サボらないための環境を提供します。

誰のどんな課題を解決するのか?

  • 学生、社会人などテストや資格取得に向けて勉強しなければならない人。
  • 自宅で1人で勉強をしていても、ついサボってしまう。
  • カフェやコワーキングスペースなどは、お金がかかるので毎日通うのは難しい。

なぜそれを解決したいのか?

マクロ視点

日本の勉強に対する意識について調査を行った。

その結果、日本人の平均学習時間は世界最低レベルであることが分かった。そこで「勉強しなければならないがそれができない人」に対して、勉強を継続できる場を与え、個人の学習時間を少しでも増やし、日本人の学習量を増やしたい。

ミクロ視点

1. 現状の課題(2つの大きな壁)

  • 勉強しなければいけないのに面倒くさいと感じてしまう。
  • 勉強を始めてもいつの間にか集中が途切れてしまいスマホをイジっている。

2. ゴール(本来あるべき状態)

  • 面倒くさい以上に勉強をするのが楽しみ
  • 勉強しているときにサボることがなくなる

どうやって解決するのか?

1. 課題の解決方法

面倒くさい→

  • 面倒くさいことを減らす
  • 面倒くさい以上の楽しみを作る

集中できない→

  • 集中できない原因を取り除く
  • 集中できる環境を作る

2. 具体的な解決策

1. 面倒くさい → 面倒くさいことを減らす

  • 画面構成をシンプルに
  • ローディングは極力減らす
  • 簡単に環境を用意できる

2. 面倒くさい → 面倒くさい以上の楽しみを作る

  • 勉強するほどポイントがもらえる
  • 共有したくなるような画面をつくる(勉強している自分イケてる)
  • 連続ログインボーナス
  • フレンドと勉強
  • ランキング機能
  • 目標を掲げる

3. 集中できない → 集中できない原因を取り除く

  • スマホと連携する機能(連携しているとポイント獲得率UP)
  • モーションキャプチャでサボっていたら通知
  • サボっている人を注意できる

4. 集中できない → 集中できる環境を作る

  • 他人と同じ環境に置く(人が頑張っているから自分も頑張ろうという気持ち)
  • 音楽再生機能(BGM、環境音など邪魔にならないものだけ)

競合調査

  • MyroomNeo
    専用のZoomに招待する。手元のみを映す。見られている環境で集中することはできる。
    常時ではないが管理人が監視している。

    デメリット → 有料である。手元とはいえカメラに移さなければならない。

  • Herazika
    モザイクでプライバシーが保護されている。

    デメリット → 有料である。現在期間限定で500円/月(本来は3000円/月)1コマ25分で区切られているため、連続して入室ができない。事前に予約が必要。

  • StudyCast(スタキャス)
    ベースは学習記録管理アプリ。オンライン自習室では、友達と一緒に勉強できる。

    デメリット → ターゲットは中高生であり、社会人向けではない。

要件定義

要件定義では、現状の課題と、それを解決する方法を以下のように考えました。

要件定義

開発の目的(Why)

1. 現状の課題(2つの大きな壁)

  • 勉強しなければいけないのに面倒くさいと感じてしまう。
  • 勉強を始めてもいつの間にか集中が途切れてしまいスマホをイジっている。

2. ゴール(本来あるべき状態)

  • 面倒くさい以上に勉強をするのが楽しみ
  • 勉強しているときにサボることがなくなる

どのように課題を解決するか(What)

1. 課題の解決方法

面倒くさい→

  • 面倒くさいことを減らす
  • 面倒くさい以上の楽しみを作る

集中できない→

  • 集中できない原因を取り除く
  • 集中できる環境を作る

2. 具体的な解決策

1. 面倒くさい → 面倒くさいことを減らす

  1. 画面構成をシンプルに
  2. ローディングは極力減らす
  3. 簡単に環境を用意できる

2. 面倒くさい → 面倒くさい以上の楽しみを作る

  1. 勉強するほどポイントがもらえる
  2. 共有したくなるような画面をつくる(勉強している自分イケてる)
  3. 連続ログインボーナス
  4. 学習記録をグラフ化
  5. フレンドと勉強
  6. ランキング機能
  7. 目標を掲げる

3. 集中できない → 集中できない原因を取り除く

  1. スマホと連携する機能(連携しているとポイント獲得率UP)
  2. モーションキャプチャでサボっていたら通知
  3. サボっている人を注意できる

4. 集中できない → 集中できる環境を作る

  1. 他人と同じ環境に置く(人が頑張っているから自分も頑張ろうという気持ち)
  2. 音楽再生機能(BGM、環境音など邪魔にならないものだけ)

学習における他人の影響について

要件

()内の数字は「具体的な解決策」に対応

機能要件

  • ログイン機能
  • オンライン自習室機能(4-1)
  • ポイント付与機能(2-1)
  • サボっている人に対して注意できる機能(3-3)
  • 音楽再生機能(4-2)
  • 学習時間グラフ化(2-4)

非機能要件

  • SPAの構成で直感的に操作できる(1-1)
  • 通常のページ移動は1秒以内(1-2)
  • マッチングは15秒以内(1-2)

ワイヤーフレーム

デザインを作成するまえに、大まかな画面のレイアウトと、遷移図も兼ねたワイヤーフレームを作成しました。

wire-frame

デザイン

ターゲットユーザーは大学生、社会人であるため、大人っぽいデザインを意識し、黒を貴重としたUIにしました。文字を主要なコンテンツとする場合、背景色は白である方が読みやすいのですが、今回はそうではないため、UX的にも問題ないと思い思い切って黒を選択しました。

design

https://www.figma.com/file/GZYkRPPiarKJCDTZhZU5Dd/sabo-learn-(公開用)?type=design&node-id=0-1&mode=design&t=JXsmlzCUXlR4WaHz-0

使用技術

  • 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

Backend (Rails + Nginx)

主要gem

  • jwt: jwt認証を実装するため。発行したtokenをfront側へ送りcookieにセット。このtokenをbackend側で検証することで、ログイン状態を判定。
  • rubocop: Rubyの静的コード解析。
  • rspec-rails: テスト用フレームワーク。
  • factory_bot_rails: テスト用のダミーデータを作成。
  • database_cleaner: テスト実行時にデータベースを初期化。
  • pry-rails: デバッグツール。

Frontend (Next.js)

主要ライブラリ

  • ress: リセットCSS。
  • sass: cssをscssで記述するため。
  • dnd-kit: ドラッグ&ドロップでの並び替え機能を実装するため。
  • react-countup: カウントアップアニメーションを簡単に実装できる。
  • socket.io-client: socket.ioを利用するため。

Socket Server (Node.js / Socket.io)

主要ライブラリ

  • nodemon: コードの変更時に自動でサーバーを再起動してくれる。
  • socket.io: サーバーとクライアントの間のリアルタイム通信を行うため。WebRTCでsdp/candidateを交換する目的で導入。

インフラ

AWSを中心に以下の図に示したインフラを構築しました。

構成図

インフラ構成図

Amplify

Next.jsを使った開発のため、Vercelへのデプロイも考えましたが20ドル/月かかります。ユーザー数が少ないうちはAWSを使う方が料金を抑えられるためAWSのサービスを使用しました。Cloudfront + S3を自分で構築する方法もありますが、実装期間が限られていることもあり、簡便にgithubと連携してプルリクベースでデプロイもできるAmplifyを選択しました。

Certificate Manager

https通信を行うため、SSL/TLS証明書を管理しています。

Route53

お名前.comで取得したドメインをルーティングさせています。フロント用とバックエンド用にサブドメインを設定しました。

Application Load Barancer

障害時やオートスケール時の入力トラフィックを適切なコンテナに分散させています。

ECR/ECS(Fargate)

Dockerベースでの開発を行ったため、ECRを採用しました。ECSを選択することで、EC2に比べてインフラの管理がラクになるのが最大のメリットです。もう一つの採用理由はECSを使った構成をやったことがないためというのがあります。運用コストのことを考えると、Fargateを採用することで、パフォーマンスに対するコンピューティングコストは、ECS on EC2に比べると割高になってしまうため、今後の検討が必要ではある部分だと考えています。

CloudWatch SNS ChatBot

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)のエラー監視。

ER 図

ER図

テーブルの役割一覧

テーブル名 役割
users ユーザー情報を管理
musics 部屋のBGMである音楽
music_purchases ユーザーが購入した音楽を管理する中間テーブル
wallpapers 部屋の背景画像
wallpaper_purchases ユーザーが購入した壁紙を管理する中間テーブル
playlist ユーザーが作成したプレイリスト
playlist_musics プレイリストに登録されている音楽を管理する中間テーブル

そこまで、複雑ではないかと思いますが、正規化として、users - musics、users - wallpapers、playlist - musicsはそれぞれ多対多の関係にあるため、中間テーブルを作成しました。

機能一覧

ユーザー利用機能

ログイン機能

  • googleアカウントを利用したユーザー登録(OAuth認証)

自習室機能

  • 滞在時間に応じてコインが付与される
  • オーディオプレイヤーで音楽を再生できる
  • 動体検知カメラによりユーザーのサボりを判定(サボり判定中は時間が止まる)
  • (マルチ部屋の場合)最大4人までのビデオ映像が画面に表示される

ショップ機能

  • 獲得したコインを消費して、背景画像、音楽を購入することができる

設定機能

  • ソロ部屋、マルチ部屋それぞれに、購入した背景画像を設定することができる
  • 購入した音楽からプレイリストを作成し、オーディオプレイヤーで再生できる

UI

  • プレイリストはドラッグ&ドロップで作成。曲順も設定可能
  • スマホ(縦横両方)、タブレットへのレスポンシブ対応

セキュリティ

  • フロント側のログイン機能は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

「勉強するなら、誰かと一緒に勉強できると楽しそう」という安易な考えから、ビデオを繋げる機能を実装することにしました。

そのためには、「WebRTC」という技術を使うみたいだというところから、「P2P通信」「SDP」「candidate」「シグナリングサーバー」「STUN/TURNサーバー」と、調べれば調べるほど、知らない単語がたくさんでてきて、正直諦めかけましたが、ひとつずつ知識を増やしながら進めていくことで、実装することができました。

備忘録として、別の記事にもまとめています。

https://zenn.dev/makoto00000/articles/2665d90fe95f3a

Amplifyへのデプロイ

フロントエンドのデプロイ先として、Next.jsを使用していたため、Vercelを考えましたが、料金的なところや、学習として自分が使ったことのないものを使ってみようという観点から、AWSのAmplifyを選択しました。

こちらも、ディレクトリを以下のように全てまとめて管理していたことが原因で、デプロイでつまづきました。

app
┣ frontend
┗ backend

試行錯誤して、ようやくデプロイにこぎつけたので、こちらも別記事に詳細をまとめています。

https://zenn.dev/makoto00000/articles/4cbfe554ff8837

ECR/ECS(Fargate)へのデプロイ

これまで学習として、EC2へのデプロイは経験していたので、こちらも新しいことへの挑戦という意味で、ECR/ECSへのデプロイに挑戦してみました。

セキュリティグループの設定だったり、環境変数の設定だったり、いろんなところでつまづきました。

そのときのツイートです↓

その後、ようやく手動でデプロイすることに成功しました。

そのときのツイートです↓

最終的には、github actionsを使った自動デプロイまで、実装することができました。

まとめ

今回のプロダクトの開発を通して、ひとつ重要なことを学びました。

それは、諦めなければ何でもできるということです。

私の嫌いな精神論ですが、事実「WebRTC」も「AWS」も最初はこんなものできるわけないと挑戦しなければ、何も生まれなかったわけで、諦めずにやってみたから実際に「できた」わけです。

思い返せば、自転車に初めて乗ったとき、車のハンドルを初めて握ったとき、やっぱり「こんなものできるわけない」と思ったものの、結局今では当たり前にできています。

人間が成長するときって、いつもこうなんだなと改めて思いました。

最初にも申し上げましたが、私は社会人として平日は仕事をしながら、帰って来てからの時間と、休日を使って、独学でプログラミングを学習してここまで来れました。

IT関連の業種ではなく、医療職でしたので、もちろん前提知識が何もないところからのスタートです。

もしこの記事が、私のように未経験から勉強しているような方の励みになれば幸いです。

最後にこのプロダクトのURLをもう一度貼っておきます。

少しでも興味を持って頂けましたら、覗いてみてください。

https://app.sabolearn.com

追記

デプロイ後にユーザーに実際に使用して頂き、インタビューを実施しました。

その中で「使い方がわからない」という意見を多く頂きました。

そこで新規登録者に対して、コーチマーク(チュートリアルみたいなもの)を表示するアップデートを実装しました。

sabolearn_coaching

また、自習室へ移動するときのローディング画面を利用して、カルーセルで簡単な使い方の説明を追加しました。

sabolearn_loading

また、今後もユーザーの反応を見ながら、より使いやすいアプリを目指してアップデートを継続していきます。

GitHubで編集を提案

Discussion

Ryota IkezawaRyota Ikezawa

瑣末な話ですが、購入するの英単語purchaseをtypoなのか誤ってかparchaceと書いており気になりました。

ウェブサービスよく設計されていて、楽しく読ませていただきました。運営応援しています!頑張ってください!

makoto hatamakoto hata

コメントありがとうございます!
ご指摘の箇所、修正しました。