モノレポにおけるgitブランチ運用との戦い

こんにちは。Another works CTOの[ソル](https://twitter.com/MotohiroSiobara)です。

最近リポジトリーの数が増えてきて、graphqlやgrpcといったリポジトリー間でのスキーマ共有をすることが増えてきたので、リポジトリーがバラバラに分かれていることに辛みを感じで、
モノレポに変えたので、その話をできればと思います。

# なぜモノレポにしたのか？

Another worksでは、いまリポジトリーの数としては10以上あって、そのほとんどが完全に分離しているサービスではなく、マイクロサービスとして依存していたり、フロントエンドをユーザーの種類によって分離しているなど、ほとんどのリポジトリーが弊社サービスの「複業クラウド」を構成するものでした。

弊社にはまだエンジニアが私含めて5人しかおらず、その中でリポジトリーが分かれすぎているため、リリースが塩漬けになるといった問題が発生していました。

また、graphqlやgrpcのほかにcdkも使っていて、こちらのコードもプロジェクトを横断で共通化していきたいため、モノレポに移行することにしました。

![スクリーンショット 2021-09-24 21.10.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114212/c3fc8b49-67a7-c131-9b78-6b2d0343d5eb.png)


# ブランチ戦略

Another worksでは、モノレポにする前はstagingブランチをdefaultブランチとしてそこから切って開発をしていました。stagingをmasterに対してマージするといったブランチの運用の仕方をしていました。

stagingブランチはpushされると自動的に検証環境に、masterブランチは本番環境にそれぞれデプロイされるようになっています。

モノレポになった時に、ブランチ運用でいろいろためしてみたので、それらを紹介していきたいと思います。

## 失敗その1 master ← staging ← feature

![スクリーンショット 2021-09-14 21.07.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114212/77cd5465-3c71-0dfb-a254-91b75c6f25c7.png)


まず初めにブランチ運用のルールを特に変えずに運用スタートしました。

その結果問題となったのが、stagingが検証環境のため、コードレビューが通った段階でマージしていたのですがapiやフロント、アプリなど複数のリポジトリーがstagingにマージされるため、それらのどれかにバグがあって本番環境にあげられないといった場合に、masterにマージできないという問題がありました。

また、検証で早めにQAしたいけど、リリースは即日では困るというようなものも、その変更が他のリポジトリーのリリースに影響を及ぼしてしまうので、結果としてリリースが遅くなるといった問題が発生しました。

## 失敗その2 master ← release/xxx ← staging ← feature

![スクリーンショット 2021-09-14 21.09.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114212/e78fc3ba-d54c-00f7-3eab-6c3243f8589b.png)


失敗その1でstagingブランチが肥大化してしまう問題を踏まえ、

stagingブランチをmasterにマージするのではなく、その中からリリースしたいものだけをcherry-pickしてreleaseブランチを作成し、それをマージするというような運用にしました。

この運用でstagingにでかいものが混ざった際にも、リリースしたいもののみを分離してリリースができるので、リリースが溜まって行ってしまうという問題は解消されました。

ですが、結論この運用も失敗でこの運用だと毎日なにかしらをリリースする弊社にとって、このcherry-pickの作業が非常に工数でした。

コミット履歴が綺麗であればまだいいですが、

検証環境に上げたあといくつかバグがあったり、どのコミットがリリースされていないといったことが明示的にわかりづらく、長い期間stagingに残ってしまうようなものだと、コミット履歴が見づらくなり、リリースの心理的なストレスも高くなってしまったため、こちらもやめました。

## 現在の運用方法

![スクリーンショット 2021-09-14 21.21.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114212/2c379f45-9a8c-c9ae-0144-d9f5779ee67e.png)


上記二つの失敗からこれらを意識して再度運用フローを作り直しました。

- cherry-pickなどはせず、PRをマージするだけでリリースがされるように
- リリースサイクルが他のリポジトリーに依存しないように

実際の運用フローはこのようになっています。

```markdown
# Pull Request命名規則

## タイトル

`[プロジェクト名] XXX`

(例)

`[fc_api] ユーザー退会APIの実装` 

# Branch命名規則

## `feature/[platform名]/*`

機能開発系のブランチ

stagingブランチから切って作業を行う

## `release/[platform名]/staging`

検証環境用ブランチ

プラットフォームごとに分かれている

## `master`

本番環境用ブランチ

## `hotfix/*`

本番環境に対する緊急リリース

masterから切ってmasterに対してマージを行う

# リリース手順

1. featureブランチを"release/[platform名]/staging"に対してマージを行う
この際に必ずsquash mergeを行う
2. release/[platform名]/stagingをmasterに対してマージする

# 基本的な心がけ

- コミットメッセージは適当に書かない
- stagingでエラーが発生したら、一度リバートして、修正を行う(コミット履歴を汚さない)
- stagingに混ぜる際は本番と同じ気持ちで(のちに開発環境を作成する)
- PRに情報をしっかりと書く
- マージしたブランチは消す
```

stagingブランチを廃止し、リポジトリーごとにstagingブランチを作成するようにしました。

例えば以下の構成だった場合に、

```markdown
- api
  - src
    - xxx
    - xxx
- front
  - src
    - xxx
    - xxx
```

`release/api/staging`と`release/front/staging` を作ります。

apiに変更があればrelease/api/stagingから切って変更をおこなうといったルールで運用を行います。

stagingブランチを分けることで、apiとfrontをそれぞれ独立してリリースすることができ、またreleaseブランチをマージするだけなので、工数も掛からずリリースをすることができます。

## それでもまだ課題は残っている

その中で残っている課題として、

masterブランチが更新された時にreleaseブランチを一つ一つマージしていかなければならない

という問題が残っています。

## Github Actionsで運用をサポート

### masterブランチに向けたreleaseブランチのpull requestを自動で作成する

```yaml
name: auto_create_pull_request

on:
  push:
    branches:
      - release/**

jobs:
  pull_request:
    name: Create Pull Request to main
    runs-on: ubuntu-latest

    steps:
      - name: Exist Pull Request
        id: exist-pull-request
        uses: actions/github-script@v4

        with:
          script: |
            const branch = context.ref.replace('refs/heads/','')
            return await github.pulls.list({
              owner: context.repo.owner,
              repo: context.repo.repo,
              head: branch,
              base: 'master',
              state: 'open',
            })
      - name: Create Pull Request
        uses: actions/github-script@v4
        with:
          script: |
            const branch = context.ref.replace('refs/heads/','')
            github.pulls.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: `${branch} 本番環境リリース`,
              head: branch,
              base: 'master'
            })
```

### PRに対してどこのディレクトリーを変更しているのか一眼でわかるようにラベルを自動でつける

`.github/labelar.yml`

```yaml
api
  - api/**/*
front
  - front/**/*
```

`.github/workflows/add_label.yml`

```yaml
name: AddLabel
on: [pull_request]

jobs:
  label:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/labeler@v2
      with:
        repo-token: "${{ secrets.GITHUB_TOKEN }}"
```

# 最後に

モノレポのいいなと思う個人的なポイントは、

会社が大きくなって、関わらないプロダクトが増えていく中で、

一つのリポジトリーを開発していることで、他のプロダクトのコードを参考にしたりする

機会が生まれやすくなるのかなと個人的には思いました。

# Another worksでは一緒に働ける仲間を探しています
https://www.wantedly.com/projects/729427

https://www.wantedly.com/projects/617507

https://www.wantedly.com/projects/683778

https://www.wantedly.com/projects/691559