30近いリポジトリを一つのリポジトリにまとめました

こんにちは!HRBrainプラットフォームチームの桜庭です。

この度HRBrainでは、アプリケーションごとにリポジトリを持つ形式から複数のアプリケーションを1つのリポジトリにまとめる形式(以下モノレポ)へ移行しました。 この記事ではいかにしてモノれぽに移行したのかを書いていきます。 アプリケーションやチームの規模その他諸々の事情によって良し悪しは変わってるのでこの記事ではモノレポとPolyrepoの比較については書きません。

どうして移行するのか

HRBrainではマイクロサービスアーキテクチャを採用しています。 そのため、提供しているそれぞれのサービス(人事評価、社員名簿、組織図、組織分析)や認証基盤などのフロントエンドとバックエンドを別なリポジトリで管理していました。 最初はそれでうまく回っていましたが、最近では以下のような問題が発生するようになりました。

  • 他のリポジトリにある共通処理を使えるようにしたい
  • リポジトリごとにPull Requestを出すときのお約束が違う
  • CIの設定等をまとめて変更するとき各リポジトリにプルリクエストを出す必要があって煩雑
  • 開発者のPCでそれぞれのサービスを起動するのが煩雑
  • APIのスキーマも別リポジトリで管理しているためそれをアプリケーションに取り込むのが面倒

これらの問題点を共有した上でモノレポとPolyrepoのメリットデメリットを社内で話し合った結果、 モノレポにするメリットがあるんじゃないかということで移行することにしました。

検討当初のポイント整理

1~3で表現していて、1が悪い3が良いです。 また、検討当初のポイント出しなので実態に沿っていない部分が少々あったりします。

要素 Polyrepo モノレポ コメント
micro frontend構成への展開容易性 3 1
プロジェクト間の疎結合の担保 3 1
セキュリティ 3 2 repositoryごとの権限管理
typescript language serverの動作の軽快さ 3 1
protoなど外部の内製ライブラリの依存管理 1 3 モノレポだと、Protobufのスキーマを変更する際に対象サービスの変更も全てまとめて1commitで完結できる
チャレンジ感 1 3
PR管理のしやすさ 3 2 モノレポの場合はサービスごとにラベルを貼るなどの工夫が必要そう。Polyrepoの方が大人数での運用は比較的しやすそう
サービス規模が大きくなったとき 2 3
CI/CD設計のやりやすさ 3 2
権限管理 3 1
コンフリクト 2 2
Git cloneコスト 3 1 時間がかかりそう
ローカル起動 2 3
チーム間のコミュニケーション 1 2 モノレポだと他サービスのへの変更が目に留まるのでチームの分断がなくなる期待
俯瞰性 1 3
go libraryの管理 2 2
package.jsonの管理 1 3
目的のコードを見つけやすさ 2 2
アプリ設定ファイルの変更容易性 1 3
CI設定ファイルの変更容易性 1 3
部分的な業務委託のしやすさ 3 2
ビギナーの理解のしやすさ 1 3
危険なcommit確率が及ぼす影響範囲の狭さ 3 1

どうやって移行したのか

今日の本題です。 移行することになったとは言うものの、開発の手を止めることはできません。 今回はちょうどよくゴールデンウィークがあったのでそのタイミングで一気に移行することにしました。

移行の要件は以下の通りです。

  • Gitの履歴を含めて移行されること
  • 各リポジトリにあるブランチが全て移行されること
  • tagは移行しない。
  • Renovateが自動で作成するブランチは移行しない

幸いなことに私たちのユースケースにほぼ合致したスクリプトを発見しました。 Gitの全ブランチの履歴を書き換ることで全てが最初からサブディレクトリにあったかのようにするスクリプトです。

dev.to

これをベースに上の要件を満たすように変更を加えてゴールデンウィーク中に実行したところ、実行開始から終了まで大体1日ちょっとかかりました。 ソースは以下に置いてあります。

github.com

移行直後にやったこと

各リポジトリのCIを統合した

弊社のCIは基本的にCircleCIまたはGitHub Actionsで動いているのでこれを1つのリポジトリにまとめる作業はあまり難しくありませんでした。 ただいくつか追加で対応しないといけないことがありました。

変更していないアプリケーションはテストを実行しないようにしたい

これ自体はすごく簡単です。 以下のように paths を追加すればいい感じにやってくれます。

on:
  push:
    paths:
      - xxx/**
  pull_request:
    paths:
      - xxx/**

しかし、GitHubのBranch protection rulesと組み合わせたときに、実行していないテストがパスしていない時はマージできないようになってしまいました。

GitHub Actionsでは同名のworkflowは最後に実行された結果が表示されることを利用して、以下のようなダミーのworkflowを追加しました。 このworkflowは変更ファイルに関係なく実行されるので、変更していないアプリケーションでもテストが通ったことにできます。

name: "Application XXX Test"
on:
  pull_request:
jobs:
  test:
    runs-on: ubuntu-20.04
    steps:
      - name: Exit with success
        run: exit 0

Git submoduleを使わないようにした

Git submoduleを使っていた箇所を全て相対パスで参照するようにしました。 これは幸い複雑なことをしていなかったため .gitmodules を消して1つずつ変更していくだけで済みました。

Pull Requestを出したときにある程度自動でラベルを付与するようにした

モノレポは全てのアプリケーションがリポジトリに含まれるので当然全てのPull Requestが集まります。 ラベルを付けて管理しないととても管理できないので変更したディレクトリに対応したラベルを付与しないといけません。

ちょっと探したらやりたいことをやってくれる Actions があったので、これをそのまま使うことで解決できました。

github.com

その他

Goのインポートパスが変わったりしたので一括置換をしたりしました。 他にもいろいろとやったような気がしますが、たくさんありすぎて覚えていません。

未解決の問題

移行に伴い、一時的に以前よりも悪くなった箇所も存在します。 順次対応していますが、たくさんあって手が回っていないのが現状です。

一部ですがその内容も紹介します。

カバレッジの計測がされなくなってしまった

変更箇所のみテストを行うのでリポジトリ全体のカバレッジ計測ができなくなってしまいました。 不完全ながらもカバレッジを Pull Requestにコメントするなどで対応するのか、モノレポでのカバレッジ計測に対応しているCodecovのようなサービスを使うのか現在も検討しています。

全アプリケーションのビルドが一斉に実行されるようになってしまった

それぞれのリポジトリに設定されていたビルドの実行トリガーをそのままモノレポに対応させたので、一斉にビルドが実行されてGoogle Cloud Buildのキューが詰まるような事態になってしまっています。 現在、必要ないビルドは実行しないようにするなどの対応を行なっており、近いうちに解決される見込みです。

まとめ

最初の提案から1年ほどかかりましたが無事にモノレポに移行できました。 モノレポへの移行自体で開発効率良くなることはありませんが、今後の土台ができたことはとても意義あることだと思っています。

ニッチな内容なので誰かの役に立つことは少ないかもしれませんが、誰かが興味を持ってHRBrainに来てくれればいいなと思っています。