tblsで始める楽々データベース設計書管理

こんにちは、コバヤシです。
弊社では、データベースのドキュメント作成にどのツールを使うべきか試行錯誤してきました。エクセルやGoogleスプレッドシートで作成してみたり、Figmaで描いてみたこともあります。しかし、どれも更新が手間で、更新を忘れてしまい、実態と合わなくなり、そのままでは使えなくなることもしばしばありました。

そんな悩みを抱えていた時、とても便利なツール「tbls」を見つけたので、今回はその紹介と使い方を紹介します。

tblsとは

tblsは、データベーススキーマを自動でドキュメント化するツールです。 MySQLやPostgreSQLなどの主要なRDBMSに対応していて、テーブル構造やリレーション情報を解析してMarkdownやHTML形式で出力できます。手動の更新が不要で、常に最新のドキュメントを維持できるのが利点です。設定ファイルを使えば、特定のテーブルやカラムを除外したり、説明を追加することも可能です。これにより、プロジェクトのスキーマ理解やチームでの情報共有がスムーズになります。

github.com

インストール方法

・Goの場合

go install github.com/k1LoW/tbls@latest

・Homebrewの場合

brew install tbls

・dockerを使う場合

docker pull ghcr.io/k1low/tbls:latest

他にもdebやRPMを使ってインストール可能です。

対応するデータベース

対応するデータベースは以下となっています。

  • PostgreSQL
  • MySQL
  • MariaDB
  • SQLite
  • BigQuery
  • Cloud Spanner
  • Amazon Redshift
  • Microsoft SQL Server
  • Amazon DynamoDB
  • Snowflake
  • MongoDB
  • ClickHouse

使い方

tblsは実際にデータベースにアクセスして構造を解析し、ドキュメントを生成します。なので、ドキュメントを作成したいデータベースにアクセスできる状態で、以下のコマンドを実行します。

mysqlの場合
tbls doc mysql://dbuser:dbpass@hostname:3306/dbname

生成されるドキュメント

tblsのdocコマンドを使うと、生成されるのはテーブルのスキーマ情報とER図です。デフォルトでは、schemaディレクトリにMarkdown形式のドキュメントと、SVG形式のER図が出力されます。

出力されるディレクトリ構造の例は以下のようになります。

├── schema
│   ├── README.md
│   ├── users.md
│   ├── orders.md
│   ├── xxxxxx1.svg
│   └── xxxxxx2.svg

概要を記載したREADME.mdファイルも自動的に生成されるため、これをGitHubやWikiにアップロードすれば、仕様書を簡単に共有できます。

tbls docコマンドのオプション

tbls docコマンドには様々なオプションがあります。

オプション 説明
-j, --adjust-table カラム幅を調整してテーブルを表示
-b, --base-url string リンク用のベース URL を指定。
-c, --config string 設定ファイルのパスを指定。
-t, --er-format string ER図の出力形式を指定(png, svg, jpg, mermaid)。
-f, --force ドキュメントを強制的に上書き。
--rm-dist 出力ディレクトリのファイルを削除してから生成。
--sort テーブルリストとカラムをソート。
--when string 実行条件を指定。(詳細はわからず)
--without-er ER図を生成しない

その他のコマンド

tbls diff

tbls diff コマンドは、データベースの現在のスキーマと、以前に生成されたドキュメントを比較し、差分を表示します。スキーマの変更点を簡単に確認でき、CIにも活用できます。

tbls lint

tbls lint コマンドは、データベーススキーマのドキュメントが正しく記述されているかを検証します。テーブルやカラムに説明が不足している箇所を指摘し、ドキュメントの品質を保つために役立ちます。これらの検証ルールは後述するtbls.ymlファイルで管理できます。

tbls coverage

tbls coverage コマンドは、データベーススキーマのドキュメントカバレッジを測定します。テーブルやカラムに対する説明やコメントがどれだけ記述されているかを可視化します。これにより、ドキュメントの品質を向上させることができます。

tbls.ymlファイル

tbls.yml ファイルは、tblsの設定ファイルです。データベースの接続情報や、ドキュメント生成時の出力設定、Lintのルールなどをの設定を定義します。

設定できる項目は以下となります。

dsn

データベースの接続設定を指定します。

dsn: mysql://dbuser:dbpass@hostname:3306/dbname

name

データベース名を指定します。

name: 書籍管理DB

docPath

出力先のパスを指定します。

docPath: doc/schema

labels

データベースにラベルを設定します。

labels:
  - books

format

ドキュメントの出力を指定します。

format:
  # Markdown 形式のテーブルの列幅を調整 デフォルトは false
  adjust: true
  # テーブルリストとカラムの順序をソート デフォルトは false
  sort: false
  # テーブルの行に連番を表示 デフォルトは false
  number: false
  # インデックスページの各テーブルのコメントを省略表示 デフォルトは false
  showOnlyFirstParagraph: true
  # 値がないテーブルのカラムを非表示 デフォルトは false カラム配列として指定可能
  hideColumnsWithoutValues: true

er

ER図の出力を指定します。

er:
  # ER図の生成をしない デフォルトは false
  skip: false
  # ER図の画像形式 (`png`, `jpg`, `svg`, `mermaid`) デフォルトは `svg`
  format: svg
  # テーブルやカラムのコメントを ER図に追加 デフォルトは false
  comment: true
  # ER図からリレーション定義を非表示にする デフォルトは false
  hideDef: true
  # ER図にカラム設定を表示する(未設定の場合、すべてのカラムを表示)
  showColumnTypes:
    # 関連カラムを表示
    related: true
    # 主キーのカラムを表示
    primary: true
  # ER図のテーブル間の距離 デフォルトは 1
  distance: 2
  # ER図のフォント デフォルトは ""(システムデフォルト)
  font: M+

lint

tbls lint コマンドでチェックする内容を指定します。

lint:
  # テーブルコメントを必要とする
  requireTableComment:
    enabled: true
    # 全てコメントがあるか、ないか
    allOrNothing: false
  # カラムコメントを必要とする
  requireColumnComment:
    enabled: true
    # 全てコメントがある、もしくはない、のどちらか
    allOrNothing: true
    # チェックから除外するカラム
    exclude:
      - id
      - created_at
      - updated_at
    # 除外するテーブル
    excludeTables:
      - logs
      - comment_stars
  # インデックスのコメントを必要とする
  requireIndexComment:
    enabled: true
    # 全てコメントがある、もしくはない、のどちらか
    allOrNothing: false
    # 警告から除外するインデックス
    exclude:
      - user_id_idx
    # 除外するテーブル
    excludeTables:
      - logs
      - comment_stars
  # 制約コメントが必要
  requireConstraintComment:
    enabled: true
    # 全てコメントがある、もしくはない、のどちらか
    allOrNothing: false
    # 除外する制約
    exclude:
      - unique_user_name
    # 除外するテーブル
    excludeTables:
      - logs
      - comment_stars
  # トリガーコメントを必要とする
  requireTriggerComment:
    enabled: true
    # 全てコメントがある、もしくはない、のどちらか
    allOrNothing: false
    # 除外するトリガー
    exclude:
      - update_count
    # 除外するテーブル
    excludeTables:
      - logs
      - comment_stars
  # テーブルラベルを必要とする
  requireTableLabels:
    enabled: true
    # 全てラベルがある、もしくはない、のどちらか
    allOrNothing: false
    # 除外するテーブル
    exclude:
      - logs
  # リレーションのないテーブルを検出
  unrelatedTable:
    enabled: true
    # 全て関連している、もしくはない、のどちらか
    allOrNothing: true
    # 除外するテーブル
    exclude:
      - logs
  # カラム数をチェック
  columnCount:
    enabled: true
    max: 10
    # 除外するテーブル
    exclude:
      - user_options
  # 必須カラム
  requireColumns:
    enabled: true
    columns:
      - name: created
      - name: updated
        exclude:
          - logs
          - CamelizeTable
  # 重複リレーションをチェック
  duplicateRelations:
    enabled: true
  # 外部キーにインデックスがあるかをチェック
  requireForeignKeyIndex:
    enabled: true
    exclude:
      - comments.user_id
  # BigQuery スタイルのラベルをチェック
  labelStyleBigQuery:
    enabled: true
    exclude:
      - schema_migrations
  # テーブルが少なくとも1つのViewpointに含まれているかをチェック
  requireViewpoints:
    enabled: true
    exclude:
      - schema_migrations

include

ドキュメント生成するテーブルを指定します。*を使ってワイルドカードでテーブルの指定もできます。

include:
  - some_prefix_*

exclude

ドキュメントの生成から除外するテーブルを指定します。

exclude:
  - some_prefix_logs
  - CamelizeTable

lintExclude

Lintチェックから除外するテーブルを指定します。

lintExclude:
  - CamelizeTable

comments

コメントを指定します。通常はスキーマ情報からコメントを取得しますが、別途コメント指定したい場合には記述します。

comments:
  -
    table: users
    # テーブルコメント
    tableComment: Users table
    # カラムコメント
    columnComments:
      email: Email address as login id. ex. user@example.com
  -
    table: posts
    # インデックスコメント
    indexComments:
      posts_user_id_idx: user.id index
    # 制約コメント
    constraintComments:
      posts_id_pk: PRIMARY KEY
    # トリガーコメント
    triggerComments:
      update_posts_updated: Update updated when posts update

relations

リレーションを指定します。通常はスキーマ情報からリレーションを取得しますが、別途リレーションを指定したい場合には記述します。

relations:
  -
    table: logs
    columns:
      - user_id
      # 親テーブル
    parentTable: users
      # 親カラム
    parentColumns:
      - id
    # リレーション定義の表示 指定しない場合は `Additional Relation`
    def: logs->users

detectVirtualRelations

リレーションを自動検出するかどうかを設定します。strategyで指定した検出方法に基づき、テーブルやカラム名からリレーションを推測して生成します。

detectVirtualRelations:
  enabled: true
  strategy: default

指定出来るstrategyと検出方法は以下です。

strategy リレーション元 リレーション先
default some_table.user_id users.id
singularTableName some_table.user_id user.id
identical some_table.user_id users.user_id
identicalSingularTableName ome_table.user_id user.user_id

dict

ドキュメント生成時に使用される単語を、指定した文字に置き換えることができます。 以下を指定するとドキュメントが日本語化されます。

dict:
  Tables: テーブル一覧
  Description: 概要
  Columns: カラム一覧
  Indexes: INDEX一覧
  Constraints: 制約一覧
  Triggers: トリガー
  Relations: ER図
  Name: 名前
  Comment: コメント
  Type: タイプ
  Default: デフォルト値
  Children: 子テーブル
  Parents: 親テーブル
  Definition: 定義
  Table Definition: テーブル定義

Viewpoints

Viewpointsは、データベース内のテーブルを論理的にまとめる機能です。これにより、関連するテーブルをまとめ、特定の視点からスキーマを整理できます。大規模なデータベースで、特定のテーブル群に焦点を当てたい場合に非常に役立ちます。

さらに、Viewpointsの下にはサブグループを作成することも可能です。これにより、階層的にテーブルをグループ化し、より詳細な分類や整理が可能になります。例えば、ユーザー管理に関連するテーブルを一つのViewpointにまとめ、その中でさらに「ユーザー情報」「認証情報」などのサブグループを作成して管理することができます。

viewpoints:
  -
    name: comments on post
    desc: Users can comment on each post multiple times and put a star on each comment.
    tables:
      - users
      - posts
      - comments
      - comment_stars
      - post_comments
      - post_comment_stars
    groups:
      -
        name: Comments
        desc: Tables about comments
        tables:
          - posts
          - comments
          - post_comments
      -
        name: Stars
        desc: Tables about stars
        tables:
          - comment_stars
          - post_comment_stars

エクセルで出力

エクセルファイルでの出力もできます。
ただER図は出力されませんでした。

tbls out -t xlsx -o schema.xlsx

CI連携

GitHub ActionsなどのCIツールに組み込むことで、Lintチェックやドキュメント生成を自動化できます。これにより、常に最新のデータベース仕様書を維持し、実態に沿ったドキュメント管理が可能になります。

GitHub Actionsに組み込むならこんな感じです。

jobs:
  run-tbls:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mariadb:10.4
        env:
          MYSQL_ROOT_PASSWORD: ${{ secrets.DB_PASSWORD }}
          MYSQL_DATABASE: ${{ secrets.DB_DATABASE }}
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=10s --health-retries=3
    env:
      DB_CONNECTION: ${{ secrets.DB_CONNECTION }}
      DB_HOST: ${{ secrets.DB_HOST }}
      DB_PORT: ${{ secrets.DB_PORT }}
      DB_DATABASE: ${{ secrets.DB_DATABASE }}
      DB_USERNAME: ${{ secrets.DB_USERNAME }}
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
      DB_CHARSET: ${{ secrets.DB_CHARSET }}
      DB_COLLATION: ${{ secrets.DB_COLLATION }}
      DB_DSN: ${{ secrets.DB_CONNECTION }}://${{ secrets.DB_USERNAME }}:${{ secrets.DB_PASSWORD }}@${{ secrets.DB_HOST }}:${{ secrets.DB_PORT }}/${{ secrets.DB_DATABASE }}
    steps:
     - name: Checkout code
       uses: actions/checkout@v4.1.0
       with:
         fetch-depth: 0
         ref: ${{ github.head_ref }}

     - name: Set up Git identity
       run: |
         git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
         git config --local user.name "github-actions[bot]"

     - name: Set up PHP
       uses: shivammathur/setup-php@v2
       with:
         php-version: '8.2'
         extensions: mbstring, xml, ctype, iconv, mysql, pdo_mysql
         tools: composer

     - name: Get composer cache directory
       id: composer-cache
       run: |
        echo "COMPOSER_CACHE_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV
       working-directory: laravel
    
     - name: Cache composer dependencies
       uses: actions/cache@v4
       with:
        path: ${{ env.COMPOSER_CACHE_DIR }}
        key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
        restore-keys: |
          ${{ runner.os }}-composer-

     - name: Composer install
       run: composer install --no-progress --prefer-dist --optimize-autoloader
       working-directory: laravel

     - name: Run migrations
       run: php artisan migrate --force
       working-directory: laravel

     - name: Install tbls 
       uses: k1low/setup-tbls@v1
     
     - name: Run tbls lint
       run: tbls lint $DB_DSN

     - name: Run tbls diff
       id: diff
       run: tbls diff $DB_DSN
       continue-on-error: true

     - name: Generate New Documentation
       if: steps.diff.outcome == 'failure'
       run: tbls doc $DB_DSN --rm-dist

     - name: Check for file changes
       run: |
         if [ -n "$(git status --porcelain)" ]; then
           echo "git_changes=true" >> $GITHUB_ENV
         else
           echo "git_changes=false" >> $GITHUB_ENV
         fi

     - name: Commit and Push changes
       if: env.git_changes == 'true'
       run: |
         git add .
         git commit -m "tbls create schema"
         git push origin ${GITHUB_HEAD_REF}

tbls lintを実行してドキュメントのLintチェックを行うのと、 tbls diffを実行して、差分があった場合のみドキュメントの再生成を行いコミットを実行します。

tblsでは、テーブル定義としてSQLの CREATE文が生成されますが、AUTO_INCREMENT が設定されたテーブルにローカルでデータがある場合、AUTO_INCREMENT の値が出力されます。一方で、GitHub ActionsなどのCI環境で実行した際、テーブルが空の場合は AUTO_INCREMENT の値が出力されません。

その結果、スキーマに実際の変更が無くても差分が発生し、不要なコミットが行われることがあります。これを防ぐためには、GitHub Actionsの実行時にデータを挿入しておくことで(例えばテストの流れでデータを投入)、AUTO_INCREMENT の差分を抑えられます。

まとめ

tblsを使用することで、データベース仕様書の作成や更新が自動化され、手動での管理の手間を大幅に削減できます。さらに、GitHub ActionsなどのCIツールに組み込むことで、Lintチェックやドキュメント生成を自動化できます。これにより、常に最新のデータベーススキーマをドキュメントに反映させ、スキーマの変更を効率的に管理できるようになります。 論理カラム名の指定や、コメント以外に説明を追加できる機能など、さらなる要望もありますが、現状の機能でも十分対応できるので良しとします。今後はtblsを使って効率的なデータベース管理を目指していきたいと思います。