🔖

コードファーストでDB定義書を自動生成したい

に公開

開発者としてはコードファーストでDBを管理しているとき、「コード(モデル + マイグレーション)を唯一のマスタ」 にして運用したい

しかし、その現実は以下の問題が現場で発生する。

1.現場で起きた問題

1-1. コードが正なのに、説明資料(定義書)が求められる

実装・運用の正は migration で確定しているので、開発チーム内では困らない。
ただし外部共有・監査・レビューの局面では、いまだに 「テーブル定義書」 の提出が必要になることが多い。

  • 取引先に提出する成果物として必要
  • テスト観点レビューで一覧性 が欲しい
  • 監査・セキュリティの資料として求められる

結果として、「コードはあるのに、別途ドキュメントを作らされる」状態になる。

1-2. 手書き定義書はすぐ陳腐化する(=二重管理になる)

一度 Excel/Google スプレッドシートで作り始めると、更新タイミングがズレる。

  • 変更は migration に入ったが、定義書が更新されていない
  • 逆に定義書は直したが、実装が追いついていない
  • どっちが正? という議論が毎回発生する

結局、「正はコード」 と言いながら、現場では “定義書も正扱いされる” という矛盾が生まれる。

1-3. SQL(migration)そのままでは読めない人がいる

「migration の SQL/コードが仕様書です」で押し切れれば理想だが、現実はそうもいかない。

  • SQLを読める人ばかりではない
  • 制約・インデックス・FK が散らばっていて追いにくい
  • 仕様書としてのフォーマット要件がある(会社のテンプレ等)

つまり、“誰でも読める形” に整形された成果物が必要になる。

1-4. レビューの観点が不足しやすい(暗黙の事故が起きる)

コードファーストだと、変更はコード差分としては分かるが、DBの観点では見落としが出やすい。

  • nullable 変更(Not Null化)
  • default の追加/変更
  • index の追加漏れ
  • FK/参照整合性の取り扱い
  • 型の微妙な違い(nvarchar(100) → nvarchar(255) など)

「DBとしてどう変わったか」が一覧で見えないため、レビューで事故る。

1-5. “今のDBの姿” を共有する方法がない

コードは Git にある。migration もある。
しかし、最新版スキーマの全体像をパッと共有する仕組みがない。

  • いちいち DB に接続して sys.tables を叩く
  • 開発環境がないと確認できない
  • 結局、誰かがスクショや手作業でまとめる

これが積み重なると、「定義書を更新しないといけない」という話に戻ってしまう。

1-6. 解決方針:マスタ(DB)からドキュメントを生成にする

これらをまとめると、やりたいことは以下

  • マスタはコード(モデル + migration)
  • DBの最終状態(適用後)から
  • 誰でも読める形式(HTML/PDF等)で
  • 定期的に(CIで)自動生成する

2. DB(sys.*)からスキーマ情報を抽出する

前章で述べた通り、マスタは コード(モデル + migration) だが、ドキュメント生成は 「migration適用後のDB」 を基準にしたい。
そのために、SQL Server が持つシステムカタログ(sys.*)からスキーマ情報を取得する。

2-1. なぜ sys.* から取るのか

コードからもある程度は生成できるが、最終的に欲しいのは DBに適用された実体。

  • 実際の型(nvarchar(255) など)
  • Default 制約
  • PK / Index / Unique
  • FK(参照関係)
  • 可能ならコメント(MS_Description)

これらは DBの最終状態を見れば確実であり、EFの実装や条件付き設定が混ざっても 今のDB を正として出せる。

2-2. 抽出する情報(最小セット)

今回は「テーブル定義書」を作るために、以下を抽出する。

  • テーブル一覧:schema / table / description
  • カラム一覧:column / type / nullable / default / PK / description
  • インデックス:index name / unique / pk / columns(includeは任意)
  • 外部キー(リレーション):親子関係 / FK名 / ON DELETE / ON UPDATE

2-3. テーブル一覧を取得するSQL

sys.tables と sys.schemas をJOINして一覧を作る。
コメントは sys.extended_properties の MS_Description を使う(任意)。

SELECT
  s.name AS schema_name,
  t.name AS table_name,
  CAST(ep.value AS nvarchar(max)) AS description
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
LEFT JOIN sys.extended_properties ep
  ON ep.major_id = t.object_id
 AND ep.minor_id = 0
 AND ep.name = 'MS_Description'
ORDER BY s.name, t.name;

2-4. カラム一覧を取得するSQL(型・NULL・DEFAULT・PK・コメント)

PKは sys.indexes.is_primary_key = 1 の sys.index_columns から列を拾う。

;WITH pk_cols AS (
  SELECT ic.object_id, ic.column_id
  FROM sys.indexes i
  JOIN sys.index_columns ic
    ON ic.object_id = i.object_id AND ic.index_id = i.index_id
  WHERE i.is_primary_key = 1
)
SELECT
  s.name AS schema_name,
  t.name AS table_name,
  c.column_id,
  c.name AS column_name,
  ty.name AS type_name,
  c.max_length,
  c.precision,
  c.scale,
  c.is_nullable,
  dc.definition AS default_definition,
  CASE WHEN pk.column_id IS NULL THEN 0 ELSE 1 END AS is_pk,
  CAST(ep.value AS nvarchar(max)) AS description
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
JOIN sys.types ty ON ty.user_type_id = c.user_type_id
LEFT JOIN sys.default_constraints dc
  ON dc.parent_object_id = t.object_id
 AND dc.parent_column_id = c.column_id
LEFT JOIN pk_cols pk
  ON pk.object_id = t.object_id
 AND pk.column_id = c.column_id
LEFT JOIN sys.extended_properties ep
  ON ep.major_id = t.object_id
 AND ep.minor_id = c.column_id
 AND ep.name = 'MS_Description'
ORDER BY s.name, t.name, c.column_id;

2-5. インデックス一覧を取得するSQL(キー列 / include列)

インデックスは sys.indexes と sys.index_columns を軸に組む。
is_included_column で include列が分かる。

SELECT
  s.name AS schema_name,
  t.name AS table_name,
  i.name AS index_name,
  i.type_desc,
  i.is_unique,
  i.is_primary_key,
  ic.key_ordinal,
  c.name AS column_name,
  ic.is_descending_key,
  ic.is_included_column
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id
JOIN sys.index_columns ic
  ON ic.object_id = i.object_id AND ic.index_id = i.index_id
JOIN sys.columns c
  ON c.object_id = t.object_id AND c.column_id = ic.column_id
WHERE i.type_desc <> 'HEAP'
ORDER BY s.name, t.name, i.name, ic.is_included_column, ic.key_ordinal;

2-6. 外部キー(FK)一覧を取得するSQL

FKは sys.foreign_keys と sys.foreign_key_columns で取得できる。
ON DELETE / ON UPDATE も *_referential_action_desc で取れる。

SELECT
  ps.name AS parent_schema,
  pt.name AS parent_table,
  pc.name AS parent_column,
  rs.name AS ref_schema,
  rt.name AS ref_table,
  rc.name AS ref_column,
  fk.name AS fk_name,
  fk.delete_referential_action_desc AS on_delete,
  fk.update_referential_action_desc AS on_update
FROM sys.foreign_keys fk
JOIN sys.foreign_key_columns fkc
  ON fkc.constraint_object_id = fk.object_id
JOIN sys.tables pt
  ON pt.object_id = fk.parent_object_id
JOIN sys.schemas ps
  ON ps.schema_id = pt.schema_id
JOIN sys.columns pc
  ON pc.object_id = pt.object_id AND pc.column_id = fkc.parent_column_id
JOIN sys.tables rt
  ON rt.object_id = fk.referenced_object_id
JOIN sys.schemas rs
  ON rs.schema_id = rt.schema_id
JOIN sys.columns rc
  ON rc.object_id = rt.object_id AND rc.column_id = fkc.referenced_column_id
ORDER BY ps.name, pt.name, fk.name, fkc.constraint_column_id;

2-7. 取得した情報を “定義書モデル” に組み立てる

抽出結果は「行」なので、そのままHTMLに出すよりも一度 中間モデルにするのが安定する。

ざっくりこんな形

  • DatabaseSpec

    • Title

    • Tables: List<TableSpec>

  • TableSpec

    • Schema, Name, Description

    • Columns: List<ColumnSpec>

    • Indexes: List<IndexSpec>

    • ForeignKeys: List<ForeignKeySpec>

このモデルを作ってしまえば、出力先は

  • Markdown

  • HTML

  • PDF

  • DBML(dbdiagram.io)

など、様々なフォーマットに変換できる

3. 出力

前章で取得した情報(tables / columns / indexes / fks)を、用途に応じて Markdown と HTML に整形して出力してみた

3-1. マークダウンで出力

利点

  • レビューしやすい
    PRで差分がそのまま見える。
    「カラム追加」「型変更」「NOT NULL化」などが、文章ではなく差分として追える。
  • リンクで辿れる(軽いドキュメントポータルになる)
    index.md(テーブル一覧)→ tables/{schema}.{table}.md(詳細)という構造にすると、GitHub上で迷子にならない。
  • 実装と同じリポジトリで完結する
    “定義書だけ別の場所にある” 問題が消える。
    仕様書の更新漏れを減らせる。

3-2. HTMLで出力

利点

  • 日本の現場で求められる“定義書フォーマット”を再現しやすい
    見出しの色、罫線、A4レイアウト、ページ区切り、ヘッダ/フッタ(作成日・版数)など。
    “いかにも提出物” な形に寄せられる。
  • PDF化が簡単(HTML→PDF)
    Playwright等でA4 PDFに変換できる。
    外部提出・監査対応でそのまま使える。
  • 成果物として扱いやすい
    ZIPで渡せる/Pagesに置ける/社内ポータルに配置できる。

4. まとめ

コードファーストでDBを管理する開発チームが抱える根本課題は、
「コードが唯一の真実なのに、他形式の成果物が求められる」という矛盾にあります。

# 課題 背景
1 コードが正でも「定義書」が求められる 外部提出・監査対応で成果物が必要
2 手書き定義書がすぐ陳腐化する 二重管理が避けられない
3 migration/SQLが読めない人がいる 関係者の非エンジニア比率が高い
4 レビューでDB差分が見えにくい 「nullable変更」「FK変更」などが埋もれる
5 「今のDB状態」を共有しづらい 実環境に接続しないと見えない

解決方針:

「コード(モデル+マイグレーション)」を唯一のマスタにし、
そこから“誰でも読めるドキュメント”を自動生成する。

これにより、次のような理想状態を実現できる。

項目 Before(従来) After(自動生成)
定義書作成 手作業でExcel CIで自動生成(Markdown/HTML)
更新漏れ 頻発 migration適用後に常に最新化
レビュー コード差分+人の記憶 Markdown差分でDB構造変化を可視化
外部共有 別作業で資料化 HTML/PDF成果物を自動出力
信頼の基準 Excel or DB or コード コード+migrationが唯一の正

5. 最後に

抽出したスキーマ情報を具体的にどうマークダウンやHtmlに落とし込んだかの具体的な処理はそのうち書くかもです。

Discussion