BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

tblsとGitHub Actionsを使ってDBマイグレーションを含むPRには自動更新したER図を追加する

BASEアドベントカレンダー2021 10日目の記事です。

f:id:budougumi0617:20211206122838p:plain
BASEアドベントカレンダー2021 10日目

BASE BANKでエンジニアをしている @budougumi0617 です。
マイグレーションファイルが含まれたPull Request(PR)が作られたとき、自動更新したER図をPRに追加するGitHub Actionsを作りました。
本記事では紹介するGitHub Actionsを利用すると次のようなメリットが得られます。

  • マイグレーションファイルをPRに出すだけでPRに更新されたER図が追加される
  • 開発者は面倒なER図の更新作業から開放される
  • レビューアはマイグレーションファイルを含んだPRをER図を見ながらレビューできるようになる
  • プロジェクト関係者は常にメインブランチのマイグレーションファイルの状態と一致したER図を確認できる

f:id:budougumi0617:20211206123336p:plain
サンプルPR
f:id:budougumi0617:20211206123420p:plain
自動生成したER図

TL;DR

  • ER図はあれば便利だけれど運用しているとメンテが大変
  • k1LoW/tblsをGitHub Actionsで動かすとマイグレーションファイルのPull Request(PR)に自動更新したER図を追加できる
    • RDBMSのスキーマをマイグレーションツールで管理しているのが前提条件
  • マイグレーションファイルの追加PRでマイグレーション後のER図を確認しながらレビューできる
  • マイグレーションファイルを書くだけでER図をメンテする必要がなくなる

サンプルリポジトリはこちらです。

github.com

GitHub ActionsのYAMLを見たい方はこちらを参照してください。 https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/.github/workflows/tbls.yml

k1LoW/tblsで自動生成しているER図はこのディレクトリにあります。 https://github.com/budougumi0617/sample_tbls_actions/tree/v0.0.1/schema/dbdoc

マイグレーションファイルを含んだPRに対してGitHub ActionsでER図を更新しているPRのサンプルはこちらです。
https://github.com/budougumi0617/sample_tbls_actions/pull/2

ER図の必要性

Webアプリケーションを開発・運用しているならば、大抵の場合RDBMSを利用していると思います。 私のチームではRDBMSのテーブル定義をrubenv/sql-migrateによるマイグレーションによって管理しています。

github.com

マイグレーションによるテーブル管理を行なっているとき問題になるのがER図の管理です。
マイグレーションファイルとER図を両方利用しているとどうやってER図を最新の状態と一致させるのかが運用上の課題になります。新規開発サービスだったり機能追加が活発なサービスの背後にあるRDBMSは毎週のようにマイグレーションが実施されます。このようなRDBMSの状態を手動でER図に反映し続けるのはかなりの労力が必要です。
また、マイグレーションファイルのレビューをするときはDDLとして正しいかだけではなく、DBの設計として妥当であるかもレビューする必要があります。そのためには他のカラム、他のテーブルを含めてマイグレーション後の姿を俯瞰的に確認する必要があります。

ER図を運用する時の課題

前述したとおりER図はあったほうが良いですがER図を作り維持し続けるには様々な課題があります。

  • 本番環境のテーブルの状態とER図がかならずマッチしている保証がない
  • マイグレーションファイルが真か?ER図が真か?
    • ER図が古いだけなのか?それともマイグレーションが意図通り行われなかったのか?
  • マイグレーションファイルを書くのが先か、ER図を書くのが先か
    • 俯瞰的にレビューするならばER図がほしい
  • オレはADD COLUMNしたいだけなんだ…
    • マイグレーションをする側からすると少しDDLを書くだけで済むはずなのにマイグレーション結果に影響しないER図の修正まで気を使うのは大変…

単純なカラム追加だけならばよいですが、外部キー制約付きのテーブル追加のようなDDLを書く場合ER図の更新は非常に煩わしいものでした。

k1LoW/tblsという救世主

メンテフリーなER図を実現するための救世主が github.com/k1LoW/tbls です。

github.com qiita.com

tblsのいいところそれだけで2記事くらい書けるので省略しますが、シンプルに設定した接続先のDBのスキーマ情報を(デフォルトでは)MarkdownとSVGファイルに出力してくれます。

f:id:budougumi0617:20211206123420p:plain
自動生成したER図

CI-FriendlyとREADMEにかかれている通りシングルバイナリでsvgファイルまで生成してくれる点もとてもありがたいです。これ系のツールでありがちな「まずはGraphvizをインストールします」ということもないのでCI上でも簡単に実行できます。

しかし、tblsは「データベースに接続してER図を自動生成するツール」です。
つまりマイグレーションを当てた状態のデータベースを用意しないとtblsを使うことができません。
マイグレーションの妥当性を確認する際もER図を見たいのに、マイグレーションしないとER図が生成できません。

そこでマイグレーションファイルを含んだPRが作られるたびにActions上でRDBMSを起動するようにActionsを書きました。

マイグレーションを含んだPRでER図を自動更新するGitHub Actions

今回作成したGitHub Actionsの全容は以下のとおりです。 https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/.github/workflows/tbls.yml

name: update er graph
on:
  pull_request:
    paths:
      - schema/sample/**
      - schema/tbls.yml
jobs:
  tbls:
    name: generate-and-push
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:5.7
        options: --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10
        ports:
          - 3306:3306
        env:
          MYSQL_ALLOW_EMPTY_PASSWORD: yes
          MYSQL_DATABASE: sample
          MYSQL_USER: sample
          MYSQL_PASSWORD: sample
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          ref: ${{ github.event.pull_request.head.ref }}
          token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
      - name: Setup go
        uses: actions/setup-go@v2
        with:
          go-version: '^1.17.1'
      - name: Execute migration
        run: |
          go get -u github.com/rubenv/sql-migrate/sql-migrate
          make up ENV=ci
        working-directory: ./schema
      - name: Execute tbls
        run: |
          curl -sL https://git.io/use-tbls > use-tbls.tmp && . ./use-tbls.tmp && rm ./use-tbls.tmp
          tbls doc -f
        working-directory: ./schema
      # tbls実行後、差分有りもしくは新規ファイルの数をカウントする
      - name: Count uncommit files
        id: check_diff
        run: |
          git status --porcelain | wc -l
          file_count=$(git status --porcelain | wc -l)
          echo "::set-output name=file_count::$file_count"
        working-directory: ./schema
      - name: Commit ER graph
        # 更新したER図をPRにコミットする
        if: ${{ steps.check_diff.outputs.file_count != '0' }}
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add .
          git commit -m "generate er graphs from actions"
          git push
        working-directory: ./schema
      # PRへ自動コミットしたらPRにコメントしておく
      - name: Report commit on pull request
        if: ${{ steps.check_diff.outputs.file_count != '0' }}
        uses: actions/github-script@v4
        with:
          script: |
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Actions committed new ER files🤖'
            })

Actionsの主な流れは次のとおりです。

  • PRにマイグレーションファイルの変更が含まれていた場合実行する
  • 事前にMySQLを起動しておく
  • マイグレーションファイルを実行する
  • tblsコマンドを実行してER図を生成する
  • もし既存のER図と差分があったら、PRに対してその差分をコミットする
  • PRにマイグレーションファイルを更新した旨をコメントする

ひとつひとつ該当するYAMLの抜粋と一緒に解説していきます。

PRにマイグレーションファイルの変更が含まれていた場合実行する

GitHub Actionsは特定のディレクトリに更新があったときのみ起動する設定が可能です。
私たちのリポジトリはアプリケーションコードと一緒にマイグレーションファイルがコミットされていますが、この設定によりアプリケーションコードを変更するだけのPRで意味もなくCIが実行されることを防ぎます。

on:
  pull_request:
    paths:
      - schema/sample/**
      - schema/tbls.yml

事前にMySQLを起動しておく

GitHub Actionsはワークフロー中にサービスコンテナという名前でDBなどのDockerコンテナを起動しておく事ができます。 docs.github.com

今回は次のような宣言でMySQLを起動しておきます。

mysql:
  image: mysql:5.7
  options: --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10
  ports:
    - 3306:3306
  env:
    MYSQL_ALLOW_EMPTY_PASSWORD: yes
    MYSQL_DATABASE: sample
    MYSQL_USER: sample
    MYSQL_PASSWORD: sample

コードをクローンしてGitHubの設定をする

コードをクローンしてGitの設定も行ないます。ここで設定したGitの設定は後半の更新したER図のコミット・pushにも利用されます。

- name: Checkout
  uses: actions/checkout@v2
  with:
    ref: ${{ github.event.pull_request.head.ref }}
    token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}

マイグレーションファイルとアプリケーションコードを同じリポジトリに入れている場合、Actionsで利用するトークンはデフォルトのトークンではなく、払い出したトークンを利用したほうがよいです。 これはデフォルトのトークンでPRに対してコミットをpushすると、そのコミットに対しては(無限ループ事故の防止のため)Actionsが起動しないためです。

docs.github.com

本記事で紹介しているActionsは起動しなくても問題ありません。しかし、アプリケーションコードとマイグレーションファイルを一緒にPRで変更していた場合ActionsからのpushでActionsが起動しないことになります。
ただ起動しないだけならばいいのですが、 CIのチェックステータスも消えるため、他のActionsでfailしていた状態も消えます
PRのBranch Protection RuleでCIのステータスを利用しているならば必ずトークンを払い出して再度Actionsが実行されるようにしておきます。

マイグレーションファイルを実行する

先ほど示したMySQLに対してマイグレーションを実行しておきます。
これでER図を生成したいDBがGitHub Actions上に構築されます。

- name: Checkout
  uses: actions/checkout@v2
  with:
    ref: ${{ github.event.pull_request.head.ref }}
    token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Setup go
  uses: actions/setup-go@v2
  with:
    go-version: '^1.17.1'
- name: Execute migration
  run: |
    go get -u github.com/rubenv/sql-migrate/sql-migrate
    make up ENV=ci
  working-directory: ./schema

make up ENV=ci コマンドは次のMakefileの設定と、dbconfig.ymlで成り立っています。

https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/schema/Makefile

ENV := "local"

up: ## Apply migration files
  sql-migrate up -env=$(ENV) -config= dbconfig.yml

https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/schema/dbconfig.yml

ci:
  dialect: mysql
  datasource: root@tcp(127.0.0.1:3306)/sample?parseTime=true
  dir: sample

tblsコマンドを実行してER図を生成する

tbls をインストールして実行します。CIフレンドリー過ぎてやることはこれだけです。あっけないですね…

- name: Execute tbls
  run: |
    curl -sL https://git.io/use-tbls > use-tbls.tmp && . ./use-tbls.tmp && rm ./use-tbls.tmp
    tbls doc -f
  working-directory: ./schema

tblsの設定自体も接続情報とsql-migrateが利用しているマイグレーション管理用のテーブルを除外するようにしているくらいです。

https://github.com/budougumi0617/sample_tbls_actions/blob/v0.0.1/schema/tbls.yml

dsn: mysql://root@127.0.0.1:3306/sample?parseTime=true

er:
  comment: true
  distance: 9
exclude:
  - gorp_migrations

もし既存のER図と差分があったら、PRに対してその差分をコミットする

先ほど実行したtblsによってER図のファイルに何らかの変化があった場合、gitのステータスに差分が出ます。 一つでも差分を見つけたらコミットを作り、PRのブランチに対してpushします。 このようにしておくことで空コミットが作られたり、Actionsが無限にループ実行することありません。

- name: Count uncommit files
  id: check_diff
  run: |
    git status --porcelain | wc -l
    file_count=$(git status --porcelain | wc -l)
    echo "::set-output name=file_count::$file_count"
  working-directory: ./schema
- name: Commit ER graph
  # 更新したER図をPRにコミットする
  if: ${{ steps.check_diff.outputs.file_count != '0' }}
  run: |
    git config user.name github-actions
    git config user.email github-actions@github.com
    git add .
    git commit -m "generate er graphs from actions"
    git push
  working-directory: ./schema

PRにマイグレーションファイルを更新した旨をコメントする

最後にPRにER図を更新した旨を書くコメントを残して終わります。

- name: Report commit on pull request
  if: ${{ steps.check_diff.outputs.file_count != '0' }}
  uses: actions/github-script@v4
  with:
    script: |
      github.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: 'Actions committed new ER files🤖'
      })

あとはPERSONAL_ACCESS_TOKENと言う名前でGitHubパーソナルアクセストークンを保存しておけば次のPRよりER図が自動生成されるようになります。

自動生成したER図が追加されたサンプルPR

上記のGitHub Actionsを使って更新されたPRが次になります。

github.com

このPRではマイグレーションファイルが一つ追加されています。
PRに対してGitHub Actionsが実行された結果、更新されたER図が追加されています。

f:id:budougumi0617:20211206123336p:plain
サンプルPR

開発者は何もせずマイグレーション後のER図をPRに追加できました。
レビューアは更新されたER図を確認することで、表で示されたテーブル構成や図示されたリレーションを見ながらレビューすることができます。

https://github.com/budougumi0617/sample_tbls_actions/blob/migrate-card/schema/dbdoc/cards.md

このPRが妥当ならばマイグレーションファイルと更新されたER図がmainブランチにマージされます。

終わりに

以上のGitHub Actionsにより、我々の開発では以下の開発者体験が実現できました。

  • GitHub上でER図をいつでもテーブル定義を確認できる
  • ER図とマイグレーション結果が一致していることを保証する
  • 開発者はER図を書く必要がない
  • マイグレーション後のER図を確認しながらマイグレーションファイルのPRをレビューできる

明日は @pigooosuke さんです!