Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Vol.08 Bill One での操作ログのテーブル設計パターン

はじめに

こんにちは。Bill One Engineering Unit の佐々木です。Bill One では主に請求書の発行機能の開発に取り組んでいます。

今回、表題の操作ログについての記事を書こうと思った動機としては、操作ログの記録はよくあるユースケースの割に都度都度、考えることが多く整理が必要と感じたためです。

なお、記事の対象としては、 BtoB のアプリケーションを開発する方を想定しています。

また、本記事は【Bill One 開発 Unit ブログリレー】という連載記事のひとつです。

操作ログとは

この記事における操作ログとは、「誰が、いつ、どのような操作を行ったか」の記録を指します。その中でも、特に記録対象として、RDB を利用したものを対象とします。
具体的には、操作ログには以下のようなデータが含まれます。

  • 操作の対象
  • 操作を行ったユーザー(場合によっては、APIなども含む)
  • 操作の種類(更新・削除・追加など)
  • 変更の内容および、変更前の内容
  • 操作を行った日時

なお、開発者によるサーバーの設定変更、データ書き換えなどの操作ログはこの記事の対象には含みません。

操作ログの目的

お客さまからの問い合わせの対応

例えば、お客さまからアプリケーション上で想定外の設定がされており原因を知りたい、という問い合わせがあった場合に、素早く回答することができます。

インシデント時の影響調査

特定の操作により発生する不具合があった場合など、素早く影響したユーザーや、データを特定することができます。

お客様側での監査

Bill One ではこの記事を書いた時点では、特定の機能を除いてお客様へのログの提供は行なっておりませんが、サービスによってはお客さまにログデータを提供し、内部監査に使用するケースもあるでしょう。

なぜ操作ログをDB に残すのか

Bill One では委託データを一般的なファイル形式のログに記録するのではなく、DB に記録する方針としています。

理由は2点あり、1点目は解約時にファイルログに含まれる委託情報の削除ができないこと、2点目は DB の方が、将来的にお客様に提供する際に抽出、加工しやすいためです。

Bill One でのテーブル設計のパターン

Bill One でよく見られる操作ログの設計パターンを3種類、取り上げます。それぞれは独立したものではなく、機能の拡大に伴い、基本となるパターン1から変遷していったものです。

前提として、Bill One ではアプリケーション共通の操作ログテーブルを持つのではなく、操作対象のデータごとに個別に操作ログのテーブルを定義しています。

ER図について

グレーのテーブルがアプリケーションのデータを記録するテーブル、水色のテーブルが操作ログを記録するテーブルになります。

なお、説明に登場する ER図の多重度は 1:1、1:多 程度に簡素化したものです。

パターン1:Bill One での典型的なテーブル設計のパターン

記録対象のデータを持つテーブルに対して、データへの一回の操作ごとに、操作ログテーブル、項目単位の操作ログテーブルを作成し、操作のたびに保存するパターンです。

パターン1

各テーブルの説明

organization

操作の記録対象のテーブルです。この例では企業などの組織情報を表すテーブルです。

organizaiton_operate_log

organization に対する一回の操作をまとめて記録するテーブルです。

action は「何をしたか」、具体的には ADD, UPDATE, DELETE などが入ります。

operated_at は「いつ」、具体的には操作を行なった時刻が入ります。

trace_id は webアプリケーションフレームワーク等が発行する追跡用のIDになります。主にファイルに出力したログの検索などに使用します。

operated_user_id は 「誰が」、具体的には別テーブルで管理するユーザーへの関係を持ちます。(ユーザー は論理削除としているため、関係を持っています。)

なお、 organization_id に FK を設定していないのは、organization が削除された場合も操作ログを残すためです。(ER図上では、便宜的に関係を記載しています。)

organizaiton_operate_log_item

「何をしたか」、具体的にはorganization に対する一回の操作に含まれる項目ごとの変更内容を記録するテーブルです。

item_type はこの例では、organization_name , zip_code などが設定されます。

before_value, after_value は基本的には 文字列型になりますが、金額など型の制約が特に必要な場合は、型ごとにテーブルを分ける場合もあります。

その他

アプリケーション側では、organizaiton_operate_log, organization_operate_log_item の更新は、ドメインイベントとして切り出し、非同期で実行することが多いです。

理由は、操作ログの記録はビジネスロジックとしては本質的な処理ではないため、ユーザーへのレスポンスを優先するためです。 厳密なトランザクションが求められる場合は、同じトランザクション内で記録対象のテーブルの更新とユーザー操作ログの記録を行うこともあるかと思います。

パターン2:ユーザー操作と、API などシステムからの操作が混在するパターン

パターン1の派生です。アプリケーションでは、外部API やバッチなどのシステムからデータを操作するユースケースがあると思います。そのような場合には、「誰が操作したか」をユーザーか、システムで区別する必要があります。

パターン2

各テーブルの説明

基本的には パターン1 と大きく変わりません。「誰が」の部分を詳細化するために organization_operate_log_operator を追加しています。

organization_operate_log_operator

「誰が操作したか」を管理するテーブルです。 operation_type には、USER, OPEN_API などが設定されます。
operated_application_id には別テーブルで持つ、APIやバッチを表す ID が設定されます。
operation_type と operated_user_id, operated_application_id の整合性は、以下のような check 制約(SQLは簡略化したものです)、およびアプリケーション側のドメイン制約によって担保します。

constraint organization_operate_log_operator_check
check ((operator_type = 'USER' AND operated_user_id IS NOT NULL AND operated_application_id IS NULL) OR
(operator_type = 'API' AND operated_user_id IS NULL AND operated_application_id IS NOT NULL))

その他

メリットとしては、パターン1 から設計変更が行いやすいことです。もともとユーザーしか操作しなかったデータに対し、新たに 外部API を追加し操作の主体が増えるケースはよくあると思います。
デメリットとしては、check 制約が複雑になりがちなことです。そうした場合は、「ユーザー」や「API」といった単位でテーブルを分けることを検討します。

パターン3:通常の項目とユーザー拡張項目のパターン

BtoB のアプリケーションには、ユーザーの運用に合わせてユーザー独自の項目(拡張項目)を定義することがあるかと思います。
このパターンは拡張項目と通常の項目が混在するパターンです。

パターン3

拡張項目とは

ここでいう拡張項目とは、アプリケーション側で用意する項目とは別に、ユーザーが独自に設定できる項目を指します。 設定できる内容としては主に以下になります。

  • ラベル
  • データ型(文字列、数値、金額等)
  • 最小文字数
  • 最大文字数
  • 最小値
  • 最大値

具体的には、今回の例では、「組織の略称」「ユーザーの基幹システムとの紐付け用のID」などが挙げられます。

各テーブルの説明

テーブルが増えて、関連も複雑になりましたね。以下にパターン1との差分を説明します。

organization_extention_field_key

拡張項目に関する設定を定義するテーブルです。
例では文字列型が前提になっていますが、場合によっては型ごとにテーブルを分ける場合もあります。

organization_extention_filed_value

ユーザーが拡張項目に設定した実際の値を保持するテーブルです。このテーブルも型によって分ける場合があります。

organization_operate_log_item

今までのパターンと基本的には変わりはありませんが、item_type には zip_code などに加えて、拡張項目の場合は extention_field が設定されます。
before_value, after_value には、organization_extention_filed_value の変更前後の値を設定します。

また、操作対象の拡張項目を特定するため、organization_extention_field_key を参照しています。

その他

メリットとしては、操作ログテーブルには1カラム追加するのみで、拡張項目への対応ができることです。

デメリットとしては、クエリが複雑になることが挙げられますが、これは拡張項目全般の問題でもあり難しいところです。

終わりに

操作ログは、よくあるユースケースの割に意外と考えなければいけないことが多いです。 今回挙げたのは一例ですが、今回挙げた例の派生か、組み合わせになることも多いと思いますので、ご参考にしていただれば幸いです。

なお、Bill Oneでは、共通の操作ログ基盤を構築する構想があります。それによって、より効率的な分析と監視を可能にすることを目指しています。

それでは、最後までお読みいただきありがとうございました。

© Sansan, Inc.