tbls と GitHub Actions を使ったスキーマ情報を管理する仕組みについて検証しました

はじめに

こんにちは、ATOM 事業本部のエンジニアの岸田 (@mwudo) です。

最近は集計基盤の機能開発や保守、API サーバ、バッチ処理などの開発をしていて、 ATOM のバックエンド周りを見ております。

現在、DBのスキーマ情報を管理する仕組みを考えています。

この記事では導入を検討している仕組みについてご紹介しようと思います。

背景

ATOM では新機能の開発や、既存機能のブラッシュアップ等で DB のテーブル定義に変更が加えられています。

このような状況下で、テーブル定義の情報やER図を人力でのメンテナンスだと追従するのが大変なのは想像できると思います。

さらに、テーブル数が多くなってきて各テーブルの関係性が複雑になってきたこともあり、新機能を開発する上でテーブル同士の関係性を理解していないと全容を把握できない機会が出てきたと感じています。

現状、テーブル同士の関係性を理解するためには、メンバーに聞くか DB のテーブルを直接みるしか方法がなく、新しく入ってくる方の認知負荷が当然高い状態です。

現状のスキーマの情報を管理して、新しく入ってくる方の立ち上がりをスムーズに行うためにも仕組みを用意しようと思いました。

新しく入ってくる方の中には新卒もいるので、より必要性を感じていました。

ツールの検討

導入するに当たって、いくつか要件を考えていました。

  1. テーブル定義とテーブル同士の関係性がわかるドキュメントを出力される
  2. CI 上(CircleCIGitHub Actions など)で実行できるとよい
  3. Git 管理をする(当然)

頭の中で要件を考えながら、日々の日課である Twitter を漁っていると、

github.com

というツールを見つけて、見た感じで考えていた要件を満たしていたのでこのツールを導入することを検討しました。

tbls

tbls は DB の情報をドキュメントすることが可能な Go 製のツールになります。

README にいくつか特徴が書かれていまして、

  1. GitHub Flavored Markdown (通称:GFM)で出力が可能(他にも様々なフォーマットにも対応)
  2. シングルバイナリ
  3. 様々な DB に対応
  4. DB の Linter としても機能する

実際にこのツールを使用して出力されるサンプルを見ると、

テーブルの一覧情報とテーブルごとの関係性が画像で示されており、わかりやすいです。

更に、各テーブルの詳細だとカラムごとの情報やインデックスなどが記載されています。

このあたりの情報は 1. テーブル定義とテーブル同士の関係性がわかるを満たしていました。

さらに、2. CI 上(CircleCI や GitHub Actions など)で実行できるとよい3. Git 管理をする(当然)に関連したところで、

GitHub Actions用の tbls が用意されていました。

github.com

tbls でやりたいことができることは確認できたので次にどのような仕組みで導入するか紹介します。

導入検討

実行環境は定義が用意されている GitHub Actions 上で行うことにしました。*1

ワークフローのイメージは以下の通りです。

  1. テーブル定義に変更を行った Pull Request (PR) がマージされた時に GitHub Actions を起動
  2. tbls を使ってスキーマ情報のドキュメントを作成・更新
  3. ドキュメントを作成・更新した PR を作成

次に各手順の GitHub Actions のワークフローの定義を紹介します。

使用する DB は MySQL で、テーブル定義は schema.sql というファイルに書かれている想定です。*2

1. テーブル定義に変更を行った PR がマージされた時に GitHub Actions を起動

# develop にプッシュされたかつ schema.sql に変更があった場合に起動
on:
  push:
    branches:
      - 'develop'
    paths:
      - "schema.sql"

READEM やテーブル定義の変更に関係ない更新で起動させないためにテーブル定義を管理しているファイルを指定しています。

2. tbls を使ってスキーマ情報のドキュメントを作成・更新

tbls を使うためにはまず、更新されたテーブル定義を DB (今回だと MySQL ) に反映させる必要があります。

update-schema-document:
  runs-on: ubuntu-latest
  services:
    mysql:
      image: mysql:5.7
      env:
        MYSQL_DATABASE: test_db
        MYSQL_ALLOW_EMPTY_PASSWORD: true
      ports:
        - 3306:3306
      # 疎通が確認できるまで待つ
      options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

次に 更新された schema.sql を反映させます。

steps:
  - name: Checkout base branch
    uses: actions/checkout@v3

  - name: Init Schema
    run: mysql -u root -h 127.0.0.1 test_db < ./schema.sql

DB へ読み込ませる処理(Init Schema)はマイグレーションツール等を採用している場合はそちらをお使いください。

これで更新されたテーブル定義を用意できたので、tbls を使ってドキュメントを作成・更新します

# 続きです
  - name: Setup tbls
    uses: k1low/setup-tbls@v1

  - name: Run tbls
    env:
      DATABASE: test_db
    # --force をつけるとすでにあるファイルに対して上書きすることが可能
    run: tbls doc --force

ここで、環境変数にDATABASEを設定しているのは、tbls には .tbls.yml or tbls.yml に設定を可能で、

# ドキュメントの出力先
docPath: doc/${DATABASE} 
# 対象のDBのDSN
dsn: mysql://root:@localhost:3306/${DATABASE} 

のように設定を追加すると、

今回だとドキュメントの出力先は doc/test_db配下に、

接続する DSN は mysql://root:@localhost:3306/test_db になります。

他にも様々な設定が可能です。

3. ドキュメントを作成・更新した PR を作成

作成したドキュメントの PR を作成しますが、どの PR に関連したもの分かっているとより親切だと思うので、このワークフローの起動のきっかけとなった PR の情報を取得したいと思います。

PR 情報の取得には GitHub CLI を使用します。

cli.github.com

今回取得する情報は以下の通りです。

  • PR の URL
  • PR のタイトル
  • PR の作成者
- name: Load PR Infomation
  id: load_pr
  run: |
    pr_url=$(gh pr list --state merged --search "sha:${{ github.sha }}" --json "url" -q ".[0].url")
    pr_title=$(gh pr list --state merged --search "sha:${{ github.sha }}" --json "title" -q ".[0].title")
    pr_author=$(gh pr list --state merged --search "sha:${{ github.sha }}" --json "author" -q ".[0].author.login")
    echo "pr_url=${pr_url}" >> $GITHUB_OUTPUT
    echo "pr_title=${pr_title}" >> $GITHUB_OUTPUT
    echo "pr_author=${pr_author}" >> $GITHUB_OUTPUT
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

--state merged を追加することで、マージされた PR の中から対象のものを検索することが可能です。*3

PR 作成には Create Pull Request を使用します。

github.com

これを使用するためには、SettingsActionsGeneral で表示されるページの下部にあるAllow GitHub Actions to create and approve pull requests にチェックをつける必要があります。

- name: Create Pull Request
  uses: peter-evans/create-pull-request@v5
  with:
    token: ${{ secrets.GITHUB_TOKEN }}
    title: "Update generated documentation"
    body: |
      [${{ steps.load_pr.outputs.pr_title }}](${{ steps.load_pr.outputs.pr_url }}) でスキーマが更新されたのでスキーマのドキュメントも更新します
    commit-message: "update generated documentation"
    base: develop
    branch: feat/update-generated-docs
    reviewers: ${{ steps.load_pr.outputs.pr_author }}
    author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

PR のイメージはこちらです。

Reviewers にはテーブル定義を変更の PR を作成した人にして、その人が確認し、問題なければマージして反映させます。

この PR 作成の部分については、PR を新規作成ではなく、テーブル定義の変更のPRに対してコミットを追加するといった方法も考えられます。

最後に各手順をまとめたファイルを載せます。

展開する

name: Update Schema Document

on:
  push:
    branches:
      - 'develop'
    paths:
        - "schema.sql"

jobs:
  update-schema-document:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:5.7
        env:
          MYSQL_DATABASE: test_db
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

    steps:
      - name: Checkout base branch
        uses: actions/checkout@v3

      - name: Init Schema
        run: mysql -u root -h 127.0.0.1 test_db < ./schema.sql

      - name: Setup tbls
        uses: k1low/setup-tbls@v1

      - name: Run tbls
        env:
          DATABASE: test_db
        run: tbls doc --force

      - name: Load PR Infomation
        id: load_pr
        run: |
          pr_url=$(gh pr list --state merged --search "sha:${{ github.sha }}" --json "url" -q ".[0].url")
          pr_title=$(gh pr list --state merged --search "sha:${{ github.sha }}" --json "title" -q ".[0].title")
          pr_author=$(gh pr list --state merged --search "sha:${{ github.sha }}" --json "author" -q ".[0].author.login")
          echo "pr_url=${pr_url}" >> $GITHUB_OUTPUT
          echo "pr_title=${pr_title}" >> $GITHUB_OUTPUT
          echo "pr_author=${pr_author}" >> $GITHUB_OUTPUT
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v5
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          title: "Update generated documentation"
          body: |
            [${{ steps.load_pr.outputs.pr_title }}](${{ steps.load_pr.outputs.pr_url }}) でスキーマが更新されたのでスキーマのドキュメントも更新します
          commit-message: "update generated documentation"
          base: develop
          branch: feat/update-generated-docs
          reviewers: ${{ steps.load_pr.outputs.pr_author }}
          author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

実際に動かしてみた

以下の画像の差分を反映させる PR を作成します。

マージ後、GitHub Actions が起動します。

すべての step が完了すると、スキーマ情報のドキュメントを更新する PR ができます。

差分を見ると、今回追加した teacher テーブルに関連した情報が追加されています。

詳細については画像の PR のリンクを貼りましたのでご覧ください。

Update generated documentation by github-actions[bot] · Pull Request #26 · shinnosuke-K/schema-documentation · GitHub

余談

.tbls.yml or tbls.yml に設定を加えることが可能と紹介しましたが、

この中で特にチームの要件にマッチしていのが、detectVirtualRelations という設定です。

detectVirtualRelations:
  enabled: true
  strategy: singularTableName

上記の設定をすることで、外部キー制約を付けていなくも user.school_idschool.id の外部キーとして扱ってドキュメントに反映してくれます。

この仕組みは、過去の経緯から外部キー制約を採用していない自分のチームとしては大変ありがたいものです。

以前、頑張って外部キー制約をつけて ER 図を作成したことがあったのです中々大変でした

まとめ

DB のスキーマ情報を管理する仕組みとして、tblsGitHub Actions を使ったものを紹介しました。

やりたかったことをできたので、メンバーの意見を踏まえてブラッシュアップして導入をしていきたいと思います!

*1:最後に PR を作成するので GitHub Actions で行ったほうがやりやすいもの理由の1つです

*2:SQL ファイル以外で管理されている場合は、適宜読み替えていただければと思います

*3:デフォルトでは open の PR から検索されます