inSmartBank

B/43を運営する株式会社スマートバンクのメンバーによるブログです

データベースのメタデータ整備をRails generatorで楽にする工夫

こんにちは、アプリケーションを開発する皆さんはデータベースのテーブルやカラムにコメントを書いていますか?本記事ではテーブルコメントやカラムコメントといったメタデータについて以下の点をご紹介します。

  • メタデータ未整備だったSmartBank社内で「メタデータ大事だね…」と理解するにいたった背景をほんの少し
  • 既存のテーブルやカラムへのメタデータ追加作業を簡易化する小さい工夫(こちらが本旨です)

SmartBankが提供するプロダクトB/43の開発で主に使用しているRailsとMySQLを前提とした内容になりますが、アイデア自体は他の言語やデータベース製品でも有用かと思います*1

データベースのメタデータとは?

MySQLなどのデータベース製品ではテーブルやカラムにコメントを付与することができます。本記事ではこれらのコメントのようなデータを説明するためのデータをメタデータと呼びます。

MySQLの場合ですとこれらのメタデータにはINFORMATION_SCHEMAを介して以下のようにアクセスできます。

-- テーブルコメントを取得
SELECT TABLE_NAME, TABLE_COMMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'your_schema_name';
+----------+-------------+
|TABLE_NAME|TABLE_COMMENT|
+----------+-------------+
|accounts  | 口座         |
+----------+-------------+
|users     | ユーザー      |
+----------+-------------+
-- 任意のテーブルのカラムコメントを取得
SELECT COLUMN_NAME, COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'users';
+-----------+--------------+
|COLUMN_NAME|COLUMN_COMMENT|
+-----------+--------------+
|id         | ID           |
|status     | 登録状態      |
|created_at | 作成日時      |
|updated_at | 更新日時      |
+-----------+--------------+

このようなメタデータを整備することでエンジニアに限らずデータを扱うすべての人がテーブルやカラムの意味や目的を理解しやすくなります

メタデータ管理については、はてな社の以下の記事も参考になりますので合わせてどうぞ。

developer.hatenastaff.com

また、記事中でご紹介されている@yuzutas0さんのtweetではなるべく前工程でメタデータを付与することの重要性*2を説いており、データ基盤の文脈で多くの現場を見てこられた方のご意見として大いに参考になりました。

弊社の抱えている課題

メタデータを理解したところで弊社の課題が何なのかを少しだけ述べます。正直に書くと、弊社が開発するシステムではこれまでメタデータの整備をやってきていませんでした。つまり、データベースのテーブルやカラムにコメントを残さずに開発・運用してきました。

テーブル設計を行ったエンジニアが開発し、運用し、分析クエリも書くような状況下では問題にならなかったためです。また、テーブル設計時にはレビュー用にドキュメントを書く事が多いため、元々の設計者でなくてもエンジニアであればそのドキュメントやコードや実データを見ればおおよそのデータは理解可能なものでした。

しかし、時が流れてチームとプロダクトがスケールしてきたことで、非エンジニアでデータを見る人が増えたり、ドキュメントの記載が古くなっていたりすることが起きはじめました。"ツケ"が回ってきていよいよメタデータを整備する機運が高まってきた…というのが現状になります。

本記事は「データベースのメタデータを整備・活用したい」という大きなイシューのうち、「コメントがない既存のテーブル・カラムへのコメント追加を簡易に行いたい」というサブイシューに対するアプローチを紹介するものになります。*3

Railsにおけるコメントの追加方法

抱えている課題を解消するためには既存のテーブルやカラムにコメントを追加していく必要があることがわかりました。

手始めにRailsのdatabase migrationを使ってコメントを追加する方法をおさらいします。

新規テーブル追加時のコメント

新規テーブル追加時はcreate_tablecommentオプションを渡すことでテーブルコメントを、t.stringt.columncommentオプションを渡すことでカラムコメントを追加できます。

class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users, comment: 'ユーザー。退会してもレコードは残り続ける' do |t|
      t.string :username, comment: '一意となるユーザー名'
    end
  end
end

新規テーブルやカラム追加時のレビューにてコメントを追加してもらうようチーム内で共通認識が得られれば良さそうです。

既存のテーブルへのコメント

既に存在するテーブルに対してはActiveRecord::ConnectionAdapters::SchemaStatementschange_table_comment, change_column_commentを使うことでコメントを設定できます。

class AddCommentToUsers < ActiveRecord::Migration[7.0]
  def change
    change_table_comment  :users,            from: '', to: 'ユーザー。退会してもレコードは残り続ける'
    change_column_comment :users, :username, from: '', to: '一意となるユーザー名'
  end
end

既存のテーブルやカラムにコメントを追加するというのは結局のところ、migration scriptを全テーブル・全カラムに対して行えばよいということになります。

機械的に追加すればそれで終わりではないか?と思われるかもしれませんが、テーブル名・カラム名を機械翻訳したコメントを付与するだけでは意味あるメタデータになりません。設計者の意図をしっかり残すにはテーブルとカラム1つ1つを精査してコメントを書いていく作業が必要になります。

Rails generatorを使ってmigration scriptを作成する

コメント追加を人がやらなければならないのはわかりつつ、しかしながら、都度migration scriptを作成しテーブルとそのカラムを全部列挙するのは面倒です。

そこで、コメント追加のコスト低減を図るため、今回の目的にかなうmigration script generatorをRails generatorとして作成してみました。

Rails generatorとは?

Railsで開発したことがあるエンジニアによってgeneratorは馴染み深いものですが一応おさらいしておくと、migration scriptのような定型的なファイル作成作業をCLIから行えるようにする便利な機構です。

公式のガイドにある具体例を引用すると、利用者が以下のようなコマンドを実行することで

$ bin/rails generate migration AddPartNumberToProducts

db/migrate以下にタイムスタンプ付きのmigration scriptファイルが作成されます。

e.g. 20230117073431_add_part_number_to_products.rb

class AddPartNumberToProducts < ActiveRecord::Migration[7.0]
  def change
  end
end

この例でいえば、開発者が「現在時刻のタイムスタンプを持つファイルを作成する」作業がなくなり便利というわけです。使い方・作り方は公式ガイドに詳しく記述されているので興味があればぜひ読んでみてください。

コメント追加用migration script generatorのコード

今回の用途で作成した3ファイルの構造とコードを記述します。

$ tree lib/generators
lib/generators
└── comment_migration
    ├── USAGE
    ├── comment_migration_generator.rb
    └── templates
        └── migration.rb.erb

USAGEは使用方法のドキュメントです。

Description:
    Generate DB migration script adding comments to table and each column

Example:
    bin/rails g comment_migration <table name>

    This will create:
        db/migrate/<timestamp>_add_comment_to_<table name>.rb

    <table name> can be class name, singular, or plural.
    Any of `User`, `user`, `users` is fine.

generatorとしての処理の大部分はcomment_migration_generator.rbに記述しています。

CLIで受け取った引数をテーブル名に変換し、既存のテーブルやカラムのメタデータを取得します。このような処理もActiveRecordでできて大変便利ですね。

# lib/generators/comment_migration/comment_migration_generator.rb
class CommentMigrationGenerator < Rails::Generators::NamedBase
  def create_comment_migration_file
    @table_name            = file_name.tableize.pluralize
    @migration_class_name  = @table_name.classify.pluralize

    # 既存のテーブルコメントを取得
    @current_table_comment = ApplicationRecord.connection.table_comment(@table_name)

    # 既存のカラムを取得
    @columns               = ApplicationRecord.connection.columns(@table_name)

    @default_comments      = {
      'id' => 'ID',
      'updated_at' => '更新日時',
      'created_at' => '作成日時'
    }

    template = File.read(File.expand_path('templates/migration.rb.erb', __dir__))
    generated_file_name = "db/migrate/#{Time.now.utc.strftime('%Y%m%d%H%M%S')}_add_comment_to_#{@table_name}.rb"

    create_file generated_file_name, ERB.new(template, trim_mode: '-').result(binding)
  end
end

@default_commentsのところはoptionalですが、どのテーブルでも一律同じ値を指定すればよいカラムコメントを定義しています。「このあたりのcolumnはコメント残しても意味がないのでは?」と言う意見が出ましたが人によってコメント有無がブレるのも統一感を欠くので、ここは機械的に埋めておくことにしました。

最後に、生成されるmigration scriptのテンプレートは以下のERBファイルです。

# lib/generators/comment_migration/template/migration.rb.erb
class AddCommentTo<%= @migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
  def change
    change_table_comment  :<%= @table_name %>, from: '<%= @current_table_comment %>', to: ''
<% @columns.each do |column| -%>
    change_column_comment :<%= @table_name %>, :<%= column.name %>, from: '<%= column.comment %>', to: '<%= @default_comments[column.name] %>'
<% end -%>
  end
end

シンプルにActiveRecord::ConnectionAdapters::SchemaStatementsをループ処理で出力しているだけです。

使い方

bin/rails g comment_migration <table name>コマンドを実行すると、対象のテーブルと全てのカラムへのコメント追加のためのstatementsが含まれるmigration scriptが作られます。<table name>はクラス名でも単数形でも複数形でも構いません。

たとえば idusernameやtimestampを持つusersテーブルが存在するとして、bin/rails generate comment_migration usersコマンドを実行します。すると以下のようなmigration scriptができあがります。

e.g. 20230317021433_add_comment_to_users.rb

class AddCommentToUsers < ActiveRecord::Migration[7.0]
  def change
    change_table_comment  :users, from: '', to: ''
    change_column_comment :users, :id, from: '', to: 'ID'
    change_column_comment :users, :username, from: '', to: ''
    change_column_comment :users, :created_at, from: '', to: '作成日時'
    change_column_comment :users, :updated_at, from: '', to: '更新日時'
  end
end

コメントを追加しようと思い立ったその時にいちいちテーブルとカラムを確認・列挙しなくてよくなり、メタデータ整備活動が少し楽になりました。

メタデータ整備の現状

B/43の最も大きいシステムコンポーネントであるモノリスRailsアプリケーションには335個のテーブルが存在し、記事執筆時点(2023年3月末頃)では約10%、34個のテーブルにコメントが付与されています

メタデータ整備運動が始まったのが2023年1月なので3ヶ月で10%、単純計算ですと27ヶ月後にはすべて完了していそうです...というのだとスピード感がないですね。実際にはすべてのテーブルにメタデータが必要なわけではありません。頻繁に参照される重要なテーブルはおよそ100個程度なため、それらを優先的に対応していくのであれば2,3ヶ月程度で達成可能かと考えます。

整備したあとにいったい何が待ち受けているのか、この"運動"の全体像や構想については別記事の公開をお待ちください。

余談: ChatGPTによるメタデータ整備の可能性

ほんのおまけです。

ChatGPTを使ってコメントを自動生成するアプローチを試みてみました。使用したプロンプトは以下です。

あなたはRailsスペシャリストです。以下のcreate_table文で定義されるテーブルとカラムにコメントをつけてください。コメントはコードへのコメントではなくデータベースのテーブルとカラムへのコメントです。commentオプションを追加する形式でコードを出力してください。

 create_table "user_profiles", id: { type: :bigint, unsigned: true }, force: :cascade do |t|
    t.bigint "user_id", null: false, unsigned: true
    t.string "first_name"
    t.string "last_name"
    t.string "first_kana_name"
    t.string "last_kana_name"
    t.string "birthday"
    t.integer "default_profile_image_id", null: false, unsigned: true
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
 end

すると、まぁ予想通りですが機械翻訳レベルのコメントがつきます。

https://i.imgur.com/3MIsQ05.png

# 作成されたスクリプト
create_table "user_profiles", id: { type: :bigint, unsigned: true }, force: :cascade, comment: "ユーザープロファイル" do |t|
  t.bigint "user_id", null: false, unsigned: true, comment: "ユーザーID"
  t.string "first_name", comment: ""
  t.string "last_name", comment: ""
  t.string "first_kana_name", comment: "名(カナ)"
  t.string "last_kana_name", comment: "姓(カナ)"
  t.string "birthday", comment: "誕生日"
  t.integer "default_profile_image_id", null: false, unsigned: true, comment: "デフォルトプロファイル画像ID"
  t.datetime "created_at", null: false, comment: "作成日時"
  t.datetime "updated_at", null: false, comment: "更新日時"
end

もうちょっと文脈を与えないとこんなものかと思いますが、ER図や既存のテーブル設計ドキュメントをインプットに加える実験も今後試していきたいところです。

終わりに

「メタデータが整備されていない」という大きな問題を小さな問題に分割して継続的な改善を可能にするための工夫をご紹介しました。

私見ですが一晩では解決できない課題に継続性に立ち向かうには、取り組むためのコストを下げ、モチベーションの阻害要因を取り除くのが大事だと考えています。

今回ご紹介したのはとても小さな改善例ではありますが、このような活動の積み重ねが"開発者体験"を高めると信じてやっていきたいと思います。


本記事はSoftware Engineerの@ohbaryeが執筆しました。

SmartBankではメタデータ整備や継続的な改善を行う工夫に関心のあるエンジニアを募集しています!

*1:記事執筆時点で利用しているRails 7.0.4.3, MySQL 5.7を前提とします

*2:つまりデータベースにテーブルやカラムを追加する=DDLを書くアプリケーションエンジニアが付与する最前が最善となる

*3:「データベースのメタデータを整備・活用したい」という大きなイシューについては将来に別記事にて紹介されるかと思いますので多くは語らずにおきます

We create the new normal of easy budgeting, easy banking, and easy living.
In this blog, engineers, product managers, designers, business development, legal, CS, and other members will share their insights.