「EVENT FOLLOW」という技術イベント発見サービスをリリースしました!
- はじめに
- 自作サービスの紹介
- クライアントのサポート対象
- 主要な機能
- イベントデータの定期実行処理
- 技術スタック
- インフラ構成図
- ランニングコスト
- サービスの各種URL
- 作って学んだこと
- サービスに含めなかった機能
- 今後やりたいこと
- 振り返り
- おわりに
はじめに
FJORD BOOT CAMP(フィヨルドブートキャンプ) の Webサービスを作って公開する という最終プラクティスで作成した自作サービスをこの度Webアプリ、iPhoneアプリとしてリリースしましたので、そのサービスのご紹介になります。
Speaker Deck
こちらは、FJORD BOOT CAMP内で自作サービスを発表した際のスライドになります。
本ブログでは、こちらのスライドと同様の構成でサービスの紹介を行います。
Webアプリ
リリースしたWebアプリのサービスURLです。
iPhoneアプリ
リリースしたiPhoneアプリのApp StoreのURLです。
自作サービスの紹介
本サービスのコンセプトは、興味のある技術イベントを見逃さない技術イベント発見サービスになります。これまで、Twitterのフォローしている人(以下、友達)の投稿で、面白(有益)そうなイベントが開催されていたことをイベント開催後に知ることが多かったので、これを解決できないかと思ったことが本サービスを思いついたきっかけになります。
エレベータピッチ
こちらのエレベータピッチの枠組みを用いて、サービス案の検討を行いました。
[1. サービス名]というサービスは、[2. 解決する問題]を解決したい[3. このサービスを使うターゲット]向けの、[4. サービスのカテゴリー]です。ユーザーは[5. このサービスができること]ができ、[6. 競合サービス]とは違って、[7. 差別化要素]が備わっていることが特徴です。
こちらが、作成したエレベータピッチの内容になります。
[EVENT FOLLOW]というサービスは、[自分の興味の方向に近い技術系のイベントを見逃してしまう問題]を解決したい[積極的にイベントを探さずに、自分の興味の方向に近い技術系のイベントを見つけたい人]向けの、[技術系のイベント発見サービス]です。ユーザーは[Twitterの友達がシェアしたDoorkeeperやconnpassなどのイベントを発見すること]ができ、[Doorkeeperやconnpassなどでキーワードで検索すること]とは違って、[検索せずに自分の興味の方向に近いイベントを発見できること]が備わっていることが特徴です。
https://speakerdeck.com/mh_mobile/introducing-event-follow-a-self-made-service?slide=2
1. サービス名
EVENT FOLLOW(読み: イベントフォロー)
2. 解決する問題
自分の興味の方向に近い技術系のイベントを見逃してしまう問題
3. このサービスを使うターゲット
積極的にイベントを探さずに、自分の興味の方向に近い技術系のイベントを見つけたい人
4. サービスのカテゴリー
技術系のイベント発見サービス
5. このサービスができること
Twitterの友達がシェアしたDoorkeeperやconnpassなどのイベントを発見すること
6. 競合サービス
Doorkeeperやconnpassなどでキーワードで検索すること
7. 差別化要素
検索せずに自分の興味の方向に近いイベントを発見できること
クライアントのサポート対象
WebアプリとiPhoneアプリ向けにサービスを提供しています。Webアプリは、PC版とSP版の2つで、スマホ向けにiPhoneアプリを提供しています。*1
iPhoneアプリは、クロスプラットフォーム開発ツールのFlutterで実装しました。ターゲットを絞るため、Androidアプリはターゲットから除き、iPhoneアプリのみをターゲットに提供しています。iPadアプリもデザイン調整やテストなどのリソースの都合上、ターゲットから除いています。*2
主要な機能
ユーザー側から見た機能になります。
Twitterのログイン機能
ユーザーのTwitterアカウントを使って本サービスにログインします。このログインしたユーザーの友達がシェアしたDoorkeeperやconnpassなどのイベント情報がユーザーに提供されます。
ログイン処理には、Firebase Authenticationを使って認証を行なっています。
イベント一覧の表示
Twitterでフォローしたユーザー(友達)が投稿したイベント情報を表示します。イベント情報には、イベントの開催のステータスをわかりやすくするために、5段階に分けてステータス表示しています。
また、Webアプリでは、ページネーションでイベント一覧を表示し、1ページにつき最大10件づつイベントを表示します。
iPhoneアプリの場合は、最下部にスクロールしたタイミングで、最大10件づつページの追加読み込みを行います。
イベントのソート機能
- 以下の4つの種別にもとづきイベント一覧をソートできます。
- Friend数
- 新着順
- 投稿順
- 開催が近い順
イベントの絞り込み機能
- Friend数のソート種別を選択した場合、以下の9つの時間軸の種別にもとづきイベント一覧を絞り込みできます。
- 過去8時間
- 過去24時間
- 過去2日
- 過去3日
- 過去4日
- 過去5日
- 過去6日
- 過去1週間
- All
- Friend数以外のソート種別を選択した場合、以下の4つのFriend数の種別にもとづきイベント一覧を絞り込みできます。
- Friends 1+ *3
- Friends 2+
- Friends 3+
- Friends 4+
- Friends 5+
イベント情報を投稿した友達一覧の表示
イベント情報を投稿した友達のアイコンを一覧表示します。
イベントを投稿した友達のツイートの表示
イベント情報を投稿したツイートやリツイート、引用ツイートの内容を時系列順にします。
イベントデータの定期実行処理
イベント一覧や友達一覧、ツイート一覧などのデータをユーザー側に表示するために、バックエンド側でTwitterからのイベントデータを処理する定期実行処理を開始します。
定期実行処理では、主に6つの実行処理で構成されます。
- ツイート取得
- イベント情報取得
- フォロー取得
- リツイート取得
- 引用リツート取得
- 過去のイベント削除
ツイート取得
Twitter APIのsearch APIを使って、connpassやDoorkeperなどのイベントのリンクが含まれるツイート情報を取得し、データベースに一時的に保存します。
イベント情報取得
データベースに保存されたツイートに含まれるリンクから、イベント種別*4を判定します。特定のイベント詳細のAPIを使って、イベント詳細情報を取得し、データベースに保存します。
フォロー取得
ログインユーザーのTwitterのフォロー情報を取得し、データベースに保存します。
リツイート取得
データベースに保存されたツイートのリツイートをTwitter APIのリツイート取得用のAPIを使って取得します。
引用リツート取得
データベースに保存されたツイートの引用リツイートをTwitter APIのsearch APIを使って取得します。
過去のイベント削除
24時に終了したイベント情報を一括で削除します。
技術スタック
Webアプリの場合は、フロントエンドにNuxt.js、バックエンドにRails APIモードを使っています。
フロントエンド
- Nuxt.js
- Nuxt Compositon API
- Firebase Authentication
バックエンド
アプリケーションサーバ
- Puma 5.2.2
データベース
キャッシュサーバ
- Redis
ツール
インフラ
- Docker Compose(開発環境)
- HerokuのDockerによるデプロイ(本番環境)
- Github Actions
- デプロイ
- ReviewDog
- Slack連携
- 定期的なヘルスチェック
- Lint
- Dependabot
iPhoneアプリのクライアントはFlutter実装で、バックエンドはWebアプリのバックエンドと同じRails APIモードを使用しています。
Flutter 2.0.1
- 状態管理アーキテクチャ
- Firegbase Authentication
インフラ構成図
本番環境のインフラ構成は以下のとおりです。
GitHubのdevelopブランチのソースコードがmainブランチにマージされると、GitHub Actionsを使って、本番環境のHerokuにNuxt.js用とRails APIモード用のDockerコンテナが自動デプロイされます。
ブラウザからのリクエストはNuxt.js用のコンテナが処理します。また、iPhoneアプリやNuxt.jsからのリクエストはRails APIモード用のコンテナが処理します。
PostgreSQLとRedisはHerokuのアドオンで提供され、Rails APIモード用のコンテナからリクエストされます。
定期実行処理は、Rails APIモード用のDockerコンテナのworkerプロセスで実行されます。workerプロセスでは、Clockworkで定期実行処理をスケジューリングし、Twitterのツイートやイベント収集などの処理を実行します。
ランニングコスト
WebアプリはHerokuで動作していますが、Herokuのコンテナとアドオンで月額$30くらいランニングコストがかかりました。内訳は、以下のようになっています。
- Nuxt.js(Heroku Web Dyno) $7
- Rails APIモード(Heroku Web Dyno + Worker Dyno) $14
- PostgreSQL(Heroku Addon) $9
データベースは、無料枠の1万レコードでレコード数が収まらなかったので、1000万レコードを格納できるPostgreSQLのHobbyプランを使いました。
Web Dynoに関しては、フリープランでGitHub Actionsで定期的にアクセスしてコンテナが停止しないようにする方法も検討しました。ただし、定常的にアクセスできると良いと考え、スリープ状態に移行しないHobbyプランに課金することにしました。
サービスの各種URL
WebアプリとiPhoneアプリのリポジトリURLとサービスURLをそれぞれ記載します。
Webアプリ
リポジトリURL
サービスURL
iPhoneアプリ
リポジトリURL
サービスURL(App Store)
作って学んだこと
今回の自作サービスは、バックエンド側で様々なイベント情報を取得し、それらのイベント情報を加工して表示するシンプルな構成だったと思います。サービスとしてシンプルな構成ではありますが、この自作サービスの作成を通して、以下の点について深く学べたように思います。
- API連携
- 定期実行処理
- ツール周り
- セキュリティ対策
- データベース周り
API連携
Nuxt.js とRails APIモード、iPhoneアプリ(Flutter)とRails APIモードでAPI連携を行なっています。また、API連携では、Firebase AuthenticationのJWT認証でリクエストを処理しています。
当初は、Railsでフロントエンドとバックエンドを兼ねて実装していましたが、iPhoneアプリのサポートを決定した際に、バックエンドをRails APIモードで実装し直し、フロントサーバのNuxt.jsとiPhoneアプリからRails APIモードにリクエストする方式で変更しました。
もともと、Webと連携したiPhoneアプリを自分で作れることが目標の一つだったため、API連携の実装を学べて良かったです。
定期実行処理
本サービスでは、Twitter検索からイベント情報を抽出し、ユーザーにイベント一覧を表示します。そのため、Railsのバックエンド側で定期的にイベント情報を抽出する処理が必要になっています。また、Twitter検索からイベント情報のツイートを抽出する以外にも、イベント詳細情報やフォロー情報、ツイートやリツイート情報といった取得処理も定期的に実行し、データベースに取得情報を保存する処理を行う必要があります。
これらの依存関係のある処理を正常時・異常時ともに上手に実行する方法を検討したり、サーバに負荷がかからないように定期実行処理の間隔を調整したり、いろいろと学ぶことが多かったように思います。
定期実行処理の起動自体は、HerokuのWorker DynoでClockworkを用いてスケジューリングしています。
ツール周り
今回は開発環境としてDockerを使うとともに、本番環境にも同じDockerコンテナを使ってデプロイすることができました。
その他、自動テストの一部として、スナップショットテスト用にStorybookやリクエスト・レスポンステスト用にOpenAPIを活用しました。OpenAPIはOpenAPI Generatorを使ったコードの自動生成機能なども活用してみたいです。
また、クラッシュ検出やパフォーマンス測定用にSentryやSkylightを導入しています。
セキュリティ周り
サービスをリリースして公開するといことで、CSRFやXSS、SQLインジェクションの脆弱性がないか最低限の対策を実施しました。
自作サービスでは、ログインユーザーのフォロー情報を取得する必要があるため、ログインユーザー毎のアクセストークンを保持し、データベースに格納しています。当初は、生のアクセストークンを保存していましたが、念の為暗号化したトークンを格納しています。
データベース周り
ユーザーからイベント情報をAPIに対してリクエストすると、データベースからイベント情報を取得し、ユーザー側にイベント情報のレスポンスを返却します。イベントに関連する友達の情報やツイートの情報も同時に取得するとN+1の問題が発生しました。
N+1の問題を解消するために、preloadを使った関連データの読み込みやユーザー情報やツイート情報をフロントサーバ側から非同期に取得することで、データベース周りのパフォーマンスを改善することができました。
サービスに含めなかった機能
当初は、フォローしている友達がシェアしたイベント一覧の表示機能以外に、以下の2つのイベント一覧の表示機能を初期リリースに含めることを検討していました。
Twitterのリスト毎にイベント一覧の表示
今後やりたいことの節においても後述しますが、Twitterのリスト後毎にイベントを抽出すると、より求めるセグメントのイベント情報を発見しやすくなると考えています。ただし、実装工数の観点と初期リリースには必ずしも必要がないことから、初期リリースの機能から見送りました。
友達の友達からのイベント一覧の表示
Twitterの友達の友達がシェアしたイベントを抽出すると、思いがけないイベントを発見する可能性があります。ただし、こちらも、実装工数の観点と初期リリースに必ずしも必要がないこと、友達の友達まで大量のフォロー情報を取得することによる実現可能性の点を考慮し、初期リリースの機能から見送りました。
今後やりたいこと
現状では、最低限の機能でサービスのリリースを行いました。このサービスを作成する上で、自分の興味の方向に近い技術系のイベントを見逃してしまう問題という課題を解決するために、Twitterの友達がシェアしたDoorkeeperやconnpassなどのイベントを発見することで課題解決を図りました。ただし、イベントがあることは認知していても実際にイベントに参加登録することを忘れたり、Doorkeeprやconnpass以外の有用なイベント情報を発見することができない問題もまだまだ残っています。これらの問題を解決するような機能の追加を検討しています。
その他、自作サービスの運用を通して、サービス運用の知見を蓄えて、今後の改善に繋げていけたらと思っています。
機能の追加
今後の機能追加としてこれらを予定しています。
Twitterのリスト毎にイベント一覧の表示
現状では、フォローしている友達がシェアしたイベント一覧を表示しています。フォローしている友達が属しているセグメントがそれぞれ異なるため、異なるセグメントのイベント情報が混在して表示される可能性が高くなっています。そこで、Twitterのリスト毎にイベント情報を抽出すると、より求めるセグメントのイベント情報を発見しやすくなると考えています。
connpassやDoorkeeper以外の技術イベントの発見
connpassやDoorkeeperはAPIが提供されているため、Twitter検索からこれらのイベント情報のツイートを抽出し、イベント詳細情報をAPIから抽出することができます。ただし、その他のパターン化されていない有益なイベント情報は自動的に発見することできません。これらのパターン化されていないイベント情報を何らかの方法でデータベースに格納し、APIとして提供することで本サービスからイベント情報を発見できるようにすることも検討しています。
iPhoneアプリの強化
イベント情報を発見するために、WebアプリやiPhoneアプリを起動する必要があります。よりイベント情報を発見しやすくするために、プル型だけでなく、プッシュ型の情報配信も有用であると思います。そこで、プッシュ通知によるイベント情報の配信も検討しています。その他、Apple Watchやウィジェットといった配信形態も検討しています。
運用の知見を蓄える
サービスの運用を行う上で、メトリクス評価やパフォーマンス測定などを行い、サービスの改善を図りたいと思います。
振り返り
プラクティスのフェーズ
FJORD BOOT CAMPの「Webサービスを作って公開する」のプラクティスのフェーズ毎に要した期間を調べてみました。サービス案の検討からリリースまでの間で結構な期間(約8ヶ月)を要したことが分かります。
- サービス案の検討 (7/20 〜 9/23 )
- ペーパープロトタイプを作る(9/23〜9/25)
- 自分で作るWebサービスのリポジトリを作る(10/2)
- Webサービスを作る(10/26〜4/20)
- 自分で作ったWebサービスのデザインレビューを受ける(4/20〜5/11)
- 自分で作ったWebサービスのコードレビュー(6/5〜6/10)
サービス案の検討では、5案考案しましたが、サービスの課題設定や実現可能性、類似の競合サービスにより4案ボツになりました。ただし、サービスについていろいろと考えるキッカケになったことと、本サービスを作ることができたので、ボツ案も無駄ではなかったように思います。
Webアプリ
実際に10月くらいから実装を行い、3月中旬まで主要な機能の実装を行いました。11月の後半部分は、FJORD BOOT CAMP内のLTの準備で、コミット数が落ち込んでいます。また、3月中旬から4月中旬は、iPhoneアプリを実装していたため、コミット数が落ち込んでいます。
4月中旬以降は、デザインレビューやコードレビューでおよそ1ヶ月くらいかかりました。合計コミットは約700コミットになりました。
https://github.com/mh-mobile/event_follow/graphs/contributors?from=2020-09-28&to=2021-06-29&type=c
iPhoneアプリ
主要な実装期間は、3月中旬から4月中旬の1ヶ月くらいかかりました。また、コミット数は約170コミットになりました。
Flutterの実装経験がほとんどなかったため、最近主流の実装方法を調べたり、既存のサンプルコードを読み解いて理解に努めました。手探り状態でしたが楽しかったです。
ソースコードが荒削りで、テストケース自体がまだほとんどないため、今後改善していきたいと思います。*5
おわりに
自作サービスの実装期間が大分長期化しましたが、WebアプリとiPhoneアプリを実装することができ納得感を持ってサービスをリリースすることができたと思います。また、これまで学習したことがないNuxt.jsやRails APIモード、Flutterといった技術スタックをサービスに取り入れた*6ことで、自作サービスを通じてより多くの技術を学び成長することができたと思います。
今後もサービスをより良くするために改善していきたいと思います。ぜひ技術イベントを見つける際に活用していただけると嬉しいです。また、何かバグを見つけた際は、GitHubのIssueなどでバグ報告をお願いします。
*1: iPhoneアプリに関しては、個人的にサポート対象に選定したため、FJORD BOOT CAMPのデザインレビューやコードレビューの対象外です
*2:今後、Andorid・iPad向けに提供する可能性はあります。
*3:イベントをツイートした友達が1人以上
*4:connpassかDoorkeeperのイベントサイト
*5:iPhoneアプリ(Flutter)に関しては、そもそもFJORD BOOT CAMPのデザインレビューやコードレビューの対象外なため、レビューは受けていません。
*6:FJORD BOOT CAMPのシステム開発のプラクティスでは、Vue.jsやRailsの技術スタックを主に学習します。