持続可能なデータベースドキュメント生成

この記事は Merpay Tech Openness Month 2022 の17日目の記事です。

はじめに

こんにちは。Credit Design Teamでバックエンドエンジニアをしている@tk8です。主にメルペイスマート払いに関わるマイクロサービスの開発や運用をしています。

この記事では、私のチームでの持続可能なデータベースドキュメントへの取り組みに関して紹介します。

背景

私が担当しているマイクロサービスでは歴史的経緯(*1)もあり複雑なスキーマ設計をしています。また、領域的にドメイン知識が複雑なものも多く、バックエンドエンジニアでもデータとスキーマの関連に関して理解が難しいことが多々ありました。

さらに私のチームではQAエンジニアやPMもデータベースに関するドキュメントを読むことがあり、不明点があれば都度詳しそうな人に聞いたり過去のSlackでの会話を調べたりするなど非効率な状態でした。

取り組みの1つとしてスキーマの勉強会を開催してその動画を残しておくなどはしてきましたが、口頭ではカラム1つ1つの説明をすることは時間的にも難しく、見る側にとっても大枠を理解することはできてもそれで全てを理解するのは困難です。

既にtblsを使ってテスト環境のSpannerと接続しER図とドキュメントを自動生成する仕組みを導入していましたが、プロダクト開発をしていく中で気づいたときだけドキュメントも更新するのみにとどまっており、ドキュメントの運用管理が適切に行われている状況ではありませんでした。

現状の課題をまとめると以下のような状態になっていました。

  • スキーマが複雑で、容易に理解できない
  • スキーマに関するドキュメントが更新されていない。そのため、記述が古かったり、新たなテーブルやカラムに対する記述がなかったりする

*1: 歴史的経緯の詳細は「メルペイスマート払いにおけるマイクロサービス化の軌跡」を読んでみてください。

今回の取り組み

今回は前項に挙げた課題に対して導入済みのtblsを用いて以下のようなアプローチを取りました。

スキーマが複雑で、容易に理解できない

この点に関しては主に以下の2点で改善を試みました。

  1. テーブルごとに機能や各ドメイン領域をラベリングしER図を色分けして出力する
  2. ER図生成時にあえて全てのリレーションを図示しない
    • テーブル間のリレーションが木構造になるようにして可読性を優先する

上記2つの改善でER図がどのようになったかサンプルのスキーマを用いてお見せしたいと思います。

今回の改善を入れずにサンプルのスキーマでER図を出力すると以下のようになります。
ルートとなるUsers(ユーザー)がありそれに紐づくInvoices(請求書)、Invoicesに紐づくMonthlyClear(翌月払いに関する請求明細)、MonthlyInstallments(定額払いに関する請求明細)がありそれぞれにMonthlyDebts(翌月払いの債権)、MonthlyInstallmentPartialDebts(定額払いの債権を月々の請求に分割した債権)、MonthlyInstallmentPartialDebtsに紐づくMonthlyInstallmentDebts(定額払い債権)の構成になっています。

まず、テーブルごとの色分けに関してですが、tblsの機能ではER図の色付けは正式サポートされていないので tbls.yamlに定義できるlabelsとカスタムテンプレートを用いて実現しました。

まずtbls.yamlに以下のようにlabelsを定義し、それぞれのテーブルに定義したlabelを付与します。

labels:
  - User
  - Invoice
  - Debt

comments:
 -
  table: Users
  labels:
    - User
 -
  table: Invoices
  labels:
    - Invoice
 -
  table: MonthlyClears
  labels:
    - Invoice
 -
  table: MonthlyInstallments
  labels:
    - Invoice
 -
  table: MonthlyClearDebts
  labels:
    - Debt
 -
  table: MonthlyInstallmentDebts
  labels:
    - Debt
 -
  table: MonthlyInstallmentPartialDebts
  labels:
    - Debt

次に以下のようなtemplateを定義しtbls.yamlでカスタムテンプレートを使用するように設定を追加します。
やり方は少々強引ですが、tblsではsvgファイル出力の場合text/templateでSpannerやyamlから読み込んだデータをdot形式の文字列に変換するので制御構文を埋め込みlabelを元に背景色を動的に変更しています。

{{- $sc := .showComment -}}
digraph "{{ .Schema.Name }}" {
  // Config
  graph [rankdir=TB, layout=dot, fontname="Arial"];
  node [shape=record, fontsize=14, margin=0.6, fontname="Arial"];
  edge [fontsize=10, labelfloat=false, splines=none, fontname="Arial"];

  // Tables
  {{- range $i, $t := .Schema.Tables }}

  {{ $labelName := "" }}
  {{ $bgcolor := "#EFEFEF" }}
  {{ if len $t.Labels }}
    {{ $label := index $t.Labels 0 }}
    {{ $labelName = $label.Name }}
  {{ end }}
  {{ if eq $labelName "User" }}
    {{ $bgcolor = "#00FFFF" }}
  {{ else if eq $labelName "Invoice" }}
    {{ $bgcolor = "#FFE6FF" }}
  {{ else if eq $labelName "Debt" }}
    {{ $bgcolor = "#9ACD32" }}
  {{ end }}

  "{{ $t.Name }}" [shape=none, label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="6">
                 <tr><td bgcolor="{{ $bgcolor }}"><font face="Arial Bold" point-size="18">{{ $t.Name | html }}</font> {{ if $sc }}{{ if ne $t.Comment "" }}<br /><font color="#333333">{{ $t.Comment | html | nl2br_slash }}</font>{{ end }}{{ end }}</td></tr>
                 {{- range $ii, $c := $t.Columns }}
                 <tr><td bgcolor="{{ $bgcolor }}" port="{{ $c.Name | html }}" align="left">{{ $c.Name | html }} <font color="#666666">[{{ $c.Type | html }}]</font>{{ if $sc }}{{ if ne $c.Comment "" }} {{ $c.Comment | html | nl2space }}{{ end }}{{ end }}</td></tr>
                 {{- end }}
              </table>>];
  {{- end }}

  // Relations
  {{- range $j, $r := .Schema.Relations }}
  "{{ $r.Table.Name }}":{{ $c := index $r.Columns 0 }}"{{ $c.Name }}" -> "{{ $r.ParentTable.Name }}":{{ $pc := index $r.ParentColumns 0 }}"{{ $pc.Name }}" [dir=back, arrowtail=crow, {{ if $r.Virtual }}style="dashed",{{ end }}];
  {{- end }}
}
templates:
  dot:
    schema: 'schema_template.dot.tmpl'

こうすることで定義したlabelごとにテーブルを色分けすることができます。
今回の場合だとユーザーに分類されているテーブルは水色、請求書に分類されているテーブルをピンク、債権に分類されているテーブルを緑色で色分けしています。

次に、ER図生成時に全てのリレーションを図示しないという点ですが上記ER図ではテーブル数が少ないのでぱっと見た感じでテーブル関係が理解できます。しかし、本番のスキーマはこの何倍もテーブル数が多くそれに比例してリレーションも複雑であるため自明なリレーションはあえて省略し木構造でルートから辿るとどういった構造になっているかを理解しやすくしました。

今回の例ではUserIdで定義しているカラムにリレーションがあるのは自明なのでそれを省略するだけでER図を木構造にしてUsersからInvoicesへと、InvoicesからMonthlyClears、MonthlyInstallmentsへと、またそれぞれから債権を管理するテーブルへと辿ることができるのがぱっと見て理解できます。

tblsではtbls.yamlに以下のような設定をすることで、strategyに応じてカラム名から自動でリレーションを作成してくれます。

detectVirtualRelations:
  enabled: true
  strategy: default

ただし今回は自明なリレーションを削除するのが目的のためrelationsを自分で定義しました。

relations:
 -
   table: Invoices
   columns:
     - UserId
   parentTable: Users
   parentColumns:
     - UserId
 -
   table: MonthlyClears
   columns:
     - InvoiceId
   parentTable: Invoices
   parentColumns:
     - InvoiceId
 -
   table: MonthlyInstallments
   columns:
     - InvoiceId
   parentTable: Invoices
   parentColumns:
     - InvoiceId
 -
   table: MonthlyClearDebts
   columns:
     - MonthlyClearId
   parentTable: MonthlyClears
   parentColumns:
     - MonthlyClearId
 -
   table: MonthlyInstallmentPartialDebts
   columns:
     - MonthlyInstallmentId
   parentTable: MonthlyInstallments
   parentColumns:
     - MonthlyInstallmentId
 -
   table: MonthlyInstallmentPartialDebts
   columns:
     - MonthlyInstallmentDebtId
   parentTable: MonthlyInstallmentDebts
   parentColumns:
     - MonthlyInstallmentDebtId

スキーマに関するドキュメントが更新されていない

こちらはtblsの機能にあるlintをPull RequestごとにCIで実行しスキーマ変更に対してtbls.yamlに書かれていないテーブルコメントやカラムコメントがあった場合に、FAILさせてドキュメントの未更新を検知し開発者にCIでドキュメント更新が必要な箇所を気付けるようにしました。

lintの設定はとても簡単でtbls.yamlに以下を追記しtbls lintコマンドを実行します。

lint:
  requireTableComment:
    enabled: true
  requireColumnComment:
    enabled: true
    exclude:

たとえばUsersテーブルに対してコメントが書かれていない場合、結果は以下のように出力されます。

% tbls lint
Users: table comment required.
Users.UserId: column comment required.

また、UserIdなどコメント書かなくても自明であるようなカラムを対象外にすることもできます。

lint:
  requireTableComment:
    enabled: true
  requireColumnComment:
    enabled: true
    exclude:
      - UserId
% tbls lint
Users: table comment required.

今回の取り組みではドキュメントが書かれていないテーブルやコメントに対してドキュメントを記述、また実際の仕様と照らし合わせて適切ではない箇所の更新をおこないlintが通る状態で運用を開始しました。

運用開始後、実際にテーブル追加に対するドキュメントの未更新を検知し、開発者がCIを通じてドキュメント更新の必要性に気付くことでスキーマ変更に追従してドキュメントを更新するような運用をおこなうことができています。

このように仕組み化された運用によって低コストにドキュメントが更新されないことを防いでいます。結果的に持続可能なドキュメント運用をおこなうことができています。

まとめ

今回は持続可能なデータベースドキュメント生成と題して私のチームでのドキュメントに関する取り組みの1つを紹介しました。

ドキュメントの管理に正解はなくどの組織でも少なからず課題感をもっているケースが多いと思います。ドキュメントは新メンバー加入時のオンボーディングだけでなく日々の業務をおこなう上でエンジニア、非エンジニア問わず必ず必要になる機会があります。一方でドキュメントを管理するコストは高く、優先度も低くなりがちです。

今回紹介したような質の高いドキュメントを持続的にメンテナンスできる仕組みが課題解決の1つの方法としてご参考になれば幸いです。

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加