フロントエンドをモノレポにするためにしたこと
こんにちは、スピフル開発リードエンジニアのKokiです。
今回はスピフル以外にも関わる部分の記事になります。
プログリットがどんなサービスを提供しているかは「5分で分かる」記事をデイビットさんが書いてくれているので、ぜひご覧ください!
改めて、今回はスピフル含むフロントエンドをモノレポにしたので、これまでの課題感やモノレポ化で工夫した点を紹介させてください。
プログリットでリリースしているフロントエンドプロダクト一覧
まずはフロントエンドの技術を利用するプロダクトと技術スタックについて説明させてください。
現在、プロダクトは4つと関連リポジトリが1つあります。
プロダクト
コンサルタント向け管理システム
スピフル
お客様向けのアカウント管理システム
シャドテンの動画配信サイト
関連リポジトリ
共通パッケージ
コンサルタント管理システム
プログリットでコンサルタントがお客様と二人三脚で学習を進める中でお客様の学習トレーニングの計画や学習進捗の確認など管理するシステムになります。技術スタックとしては、NextjsとMUIを使っております。
スピフル
スピフルについては、他記事で紹介させていただいているので割愛させていただきますが、スピーキングに特化した学習ができるWebアプリです。
こちらの技術スタックもNextjsとMUIを使っております。サービス概要については公式LPをぜひご覧ください!
お客様向けのアカウント管理システム
プログリットが提供しているサービスをご利用されているお客様向けにパスワードリセットやメールアドレス変更を提供しているシステムになります。
こちらは、Nextjsを使っていますがSSGを使ってS3ホスティングを行っています。
※ SSGは、ビルド時にHTMLの生成を実施し、クライアント側からリクエストされた際に、事前に生成しておいたHTMLを渡すアーキテクチャーです。
シャドテンの使い方動画配信サイト
シャドテンをご利用のお客様にシャドーイングの学習方法などを紹介するサイトで、アプリからWebViewを使って表示する前提のサイトとなっており、こちらもNextjsを使いSSGビルドしたものを配信しています。
※シャドテンはリスニング力の向上に特化したサービスになります!僕も毎日していて、添削によって音の消失、連結などを知覚することでリスニング力が上がっているのを実感しています!
共通パッケージ
以上4つのプロダクトで共通して使っているReactコンポーネントやeslintなどの設定を共通化しているものを管理しているリポジトリになります。
分散リポジトリの問題点
すべてのプロダクトでNextjsを使っていますが、リポジトリが個別にあるだけで下記の課題感が徐々に無視できなくなってきました。
Node.jsやライブラリ依存関係のアップデート
RenovateのAutomerge機能などを使い労力を減らせてはいますが、メジャーアップデートのときは結構大変
共通パッケージにコード化するときの計画の立てづらさ
Reactコンポーネントの共通化は問題ないことがほとんどですが、MUIを含めた共通化を行おうとすると、MUIのマイナーバージョン違いによる微妙な違い(アクセシビリティ更新)などがあり、どのプロダクトを主軸に共通化するのがベストなのか…など悩む時間が増え、結局共通化出来ないケースも目立ってきました
Github Actionsなど手元の細かいスクリプトが老朽化していく
スピフルなど新しいプロダクトではGithub ActionsやOpenAPIから自動生成するスクリプトなど過去の経験を活かしブラッシュアップしたものを実装しています。しかし、その後他プロダクトへの展開についてはイシュー化は出来ても中々手が付けられていません
ライブラリアップデートだけだとリリースしていない事もある
シャドテンの動画配信サイトなどは動画追加がなければ、リリース要否に関してはエンジニア判断としています。それゆえにライブラリアップデートなど細かい非機能要件については把握漏れがあったりして、リリース出来ていない事もありました
モノレポにして実現したかったもの
以上の課題感をフロントエンドチームとディスカッションしながら、Vercel製のturborepoなどモノレポで便利なエコシステムが存在していることや既にyarnを使っていることもありyarn workspaceを使うことに労力が少ないため、モノレポ化を検討してみても良いのでは?という雰囲気になっていました。
モノレポ化検討は、上記の課題感をちゃんと解消できるかつまり下記をゴール設定にしても問題ないかを検討しました。
共通したライブラリアプデはすべてまとめて行い、いつでも本番リリースできるようにする
サービスを横断的にコード共通化しやすい環境にする
本番リリース作業(主にPR作成)を自動化しリリース漏れなしとリリース作業をマージボタン押すだけの状態にする
また、ゴールを実現した後に起こりそうなネガティブな影響も同時に検討しチーム内で認識合わせを行いました。
そちらも一部抜粋してご紹介させてください。
異なるプロダクトのイシューが1リポジトリに集約されてしまい見通し悪そう
対応プラン:プロダクトが分かるラベルをイシューにつける
シークレット設定、ワークフローファイルなど管理の煩雑化、似たものが多くなり見通しが悪くなる
対応プラン1:GitHub ActionsのEnvironmentsを使う
対応プラン2:ワークフローにはプレフィックスをつける
リリースタイミングがサービスごとに違う
対応プラン1:サービスごとのmainブランチを作り、リリースPRはそれぞれにマージさせていく(main_oz, main_aesopなど)
対応プラン2:チェックボックスを設けリリースして良ければチェックを入れる
プロダクトが分かるラベルをイシューにつける
プログリットでは親イシューと子イシューに分けて仕様整理や進捗管理を行っています。
モノレポ化すると1つのリポジトリにすべてのフロントプロダクトの子イシューが集約され見通しが悪くなるのではないか?と懸念が出ました。
親イシューと子イシューについては下記の記事で分かりやすく解説してくれているので、ぜひご覧になってください。
上記の自動化でCat_◯◯といったラベルを子イシューに自動で付けてくれるようになったので、その心配はいらなくなり非機能要件など親イシューを作らないケースでは明示的につけるようチーム内で認識合わせをするだけで済みました。
しかし、旧リポジトリではCat_〇〇といったラベルはついていなかったので下記の手順で移行しました
旧リポジトリのGithubのUI上ですべてのイシューに一律Cat_〇〇をつける
旧リポジトリのイシューはスクリプトで一括Transferしてモノレポに引っ越しする
Github ActionsのEnvironmentsを使う
フロントエンドではGithub Actionsを使ってLintや単体テスト、デプロイまで行っています。モノレポ化により、シークレット値やデプロイなど似たようなワークフローファイルなどが生まれ、見通しが悪くなるのでは?と心配しました。
結果的にはGithub ActionsのEnvironments機能を使ってプロダクトごとに分けることにしました。
後述する本番ブランチと開発環境用のEnvironmentsを個別に用意することでプロジェクト感で同じシークレット名を使うことが出来ます。
Workflowファイルの中で使用するEnvironmentsを指定することでコンテキスト切り替えも容易になります。
jobs:
deploy:
name: Deploy Oz
runs-on: ubuntu-latest
environment:
name: ${{ github.ref == 'refs/heads/main_oz' && 'production_oz' || 'development_oz' }}
defaults:
run:
working-directory: apps/oz
一方、ワークフローファイルに関してはファイル名にプロダクトが分かるようプロダクト名のプレフィックスを付けるようにしました。
まだ整理の余地はありますが、プロダクトごとにまとめてソートされたりとちょっとした工夫で見通し良くなりました。
リリースタイミングがサービスごとに違う
コンサルタント管理システムなどは所謂社内システムに近い事もあり、運用周知のタイミングと機能リリースを合わせる必要があります。
一方でスピフルなどはまだリリースしたばかりで日々の改善をいち早くお客様に届けたいという思いから随時リリースを採用していたりとプロダクトごとにリリースしたいニーズが異なります。
そこで、モノレポではリリースブランチをプロダクトごとに用意し下記のような流れにしました。
補足をさせていただくと、
作業ブランチはdevelopからcheckout
作業ブランチからPRを作成し、developにマージ
developにマージするとそれぞれのリリースブランチ(main_aesopやmain_ozなど)に向けたリリースPRを生成
リリースPRをマージするとデプロイワークフローが起動する
4のプロダクトごとにあるリリースPRを任意のタイミングでマージすることにより、リリースすることが可能になります。
こちらはコンサルタント管理画面(Aesop)のリリースPRになりますが、先日別記事にて紹介したリリースPRの自動作成が使い心地良かったので、モノレポでも使うことにしました。
こういった工夫は日々模索しており、手前味噌ですが新規事業から多くの学びを得られました。
そちらについても記事書いているのでぜひ読んでください!
しかし、このリリースPRはプロダクトごとに全く同じ内容が書かれる事になります。
つまり、一つの作業PRをマージすると、4プロダクト分のリリースPRにチェックを入れにいく必要があるのです。
2週間くらいは上記の自動生成で運用していましたが、スピフルの機能開発にコンサルタント管理画面のリリースPRでチェックを入れるのはあまりにも非効率でしたし、ついつい忘れてしまうという事が起きていました。
そこでリリースPRの自動作成スクリプトを自作し、上記の並びとなるように改善しました。こちらはコンサルタント管理画面(プロダクト名:Aesop)の実際のPRになります。
リリースPRの自動生成スクリプトの詳細は省きますが、ざっくり説明すると
本番ブランチとdevelopブランチのマージされたPRを取る
PRの変更ファイル一覧を後述のpathPatternと突合して一致した場合は、プロダクト向けの変更が含まれると判定
上記PRのFeature for Aesopのエリアに追記する
という事をghコマンドを使って自前実装しました。
これにより各プロダクトに関係あるPRだけにチェックを入れる無駄のない形になりました。
プロダクトごとの設定
最後にこちらのスクリプトはymlファイルでプロダクトごとに微調整できるようになっています。Feature For Aesopの部分はglobパターンで関連あるファイルを列挙することでPRの分類を行っています。
apps:
oz:
developBranch: "develop"
productionBranch: "main_oz"
pathPatterns:
- "apps/oz/**"
- "package.json"
- "yarn.lock"
titleTemplate: "🧙♀ Oz Release {{now}}"
bodyTemplate: |
# 🧙♀ Oz Release {{now}}
# Feature for Oz
{{#prsRelatedToPaths}}
- [ ] #{{number}} {{^author.is_bot}}@{{author.login}}{{/author.is_bot}}
{{/prsRelatedToPaths}}
# Other PRs
{{#prsOther}}
- #{{number}} {{^author.is_bot}}@{{author.login}}{{/author.is_bot}}
{{/prsOther}}
aesop:
developBranch: "develop"
productionBranch: "main_aesop"
pathPatterns:
- "apps/aesop/**"
- "package.json"
- "yarn.lock"
titleTemplate: "🦊 Aesop Release {{now}}"
bodyTemplate: |
# 🦊 Aesop Release {{now}}
# Feature for Aesop
{{#prsRelatedToPaths}}
- [ ] #{{number}} {{^author.is_bot}}@{{author.login}}{{/author.is_bot}}
{{/prsRelatedToPaths}}
# Other PRs
{{#prsOther}}
- #{{number}} {{^author.is_bot}}@{{author.login}}{{/author.is_bot}}
{{/prsOther}}
最後に
以上がフロントエンドチームでモノレポ化したご紹介でした。
現在はひとまず一つのリポジトリに集約出来たという段階で、当初の目的であるライブラリアップデートをひとまとめに行うための活動はこれから実施していきます。
そちらも一段落したら記事にしてフロントエンドチームの開発環境について最新情報を公開したいと思います!
プログリットの成長を加速させる開発のお仕事を担う仲間を募集しています!
プログリットでは、プロダクト開発のメンバーを募集しています!
「世界で自由に活躍できる人を増やす」というミッションに共感してくださる方、組織の中でお互いに切磋琢磨しながら成長していきたいという方は、ぜひカジュアル面談でお話しましょう!