RDBの作成時刻や更新時刻用カラムに関するプラクティス
RDBのレコードに、作成日時や更新日時を自動で入れ込むコードを書いたりすることあると思いますが、それに対する個人的な設計指針です。ここでは、作成日時カラム名をcreated_at
、更新日時をupdated_at
として説明します。
tl;dr
- レコード作成日時や更新日時をRDBのトリガーで埋めるのは便利なのでやると良い
- ただ、アプリケーションからそれらのカラムを参照することはせず別に定義した方が良い
MySQLにおける時刻自動挿入
MySQL5.6.5以降であれば、以下のようにトリガーを設定すれば、レコード挿入時に作成日時と更新日時を、更新時に更新日時を、DATETIME型にも自動で埋めてくれます。いい時代になりました。(MySQLが遅すぎたという話もある)
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
便利なので積極的に使っていくと良いでしょう。アプリケーションがバグっていても確実に埋めてくれます。アプリケーションフレームワークでこれらの値を自動で埋めてくれるものもありますが、今の時代はトリガーを使うのが良いのではないでしょうか。
ただし、これらはログ的な調査記録用の扱いで、アプリケーションから参照することはしないようにするのが良いと考えます。
なぜアプリケーションから参照しない方が良いのか
以下のような理由です。
- トリガーにアプリケーションロジックが依存しすぎて密結合になってしまう
- アプリケーションから使うのであれば、汎用的な名前ではなく意味のある名前にすべき
トリガーにアプリケーションロジックが依存しすぎて密結合になってしまう
例えば、テストコードを書く際にRDBも一緒に動かすことが必須になってしまう。現在時刻のモックも難しい。テストの時だけINSERT文に明示的に時刻を指定するのは本末転倒です。
ちなみに矛盾するようなことを言いますが、僕は、アプリケーションのテストコードはなるべく実際のDBを起動してバンバンデータを入れながらテストするのがいいとは考えています。変に頑張りすぎてインターフェースレイヤー切っちゃうとそこがバグったりするので。
アプリケーションから使うのであれば、汎用的な名前ではなく意味のある名前にすべき
created_at
や updated_at
のような汎用的な名前ではなく、例えば、blog_entries
テーブルであれば、published_at
, edited_at
などの命名にすべきです。
これらのカラムは、created_at
や updated_at
とは別に設け、値もトリガーではなくアプリケーションから埋めるべきでしょう。
想定問答など
記録用のためであればログをしっかり残して無駄なデータを入れる必要はないんじゃないの?
これはその通りです。ログ保存の確実性と検索性が高いのであれば、別にここにいちいち記録する必要はないでしょう。ただ、多くの場合そこまでログ整備はしっかりしておらず、とりあえずRDBにクエリを打った方が手っ取り早いということも多いでしょう。ログを取り漏れている可能性もあります。多少冗長な情報はいざという時にあなたの身を助けてくれます
イミュータブルデータモデルを採用している場合更新時刻は不要ではないか?
これもその通りです。思考停止して created_at
と updated_at
を追加するのではなく、そのカラムが本当に必要なのかを考えてテーブル設計しましょう。ただ、考えすぎても案外脳のリソースを取られるので、迷ったらとりあえず入れておく、でも良いとも思います。残念ながら設計力が足りず「やっぱり更新が必要になってしまった」となることもあるでしょう(データ量が許すのであればそうなってからカラムを追加してもいいとは思いますが)。
イミュターブルデータモデルの他にも、例えば、ユーザが操作しないマスタ系のテーブルでdeploy時にそのデータを埋めるフローなのであれば、作成時刻や更新時刻は不要でしょう。
ちなみに、レコードをイミュータブルに扱うというのは、近年の安全な設計論からしても理に叶っています。なるべくイベントとして扱うというのはわかりやすい指針です。ただ、現実問題として、難しい局面もあります。データ量も多くなりがちです。データ量はパーティションなどを活用すれば解決できますが、他にも例えば「10人のユーザーの最新のイベントだけを取って一覧する」といったことが案外難しかったりします。
データが重複してデータ量が増えるのが嫌なんですが
もちろん余計なデータが増えることは避けたいですし、そのカラムが本当に必要かどうか都度検討するのが望ましいと前項で述べました。
また、上記の例ですと、created_at
とpublished_at
に同じ値が入ってしまうことが気持ち悪いかもしれません。しかし、この場合は良くないDRYの落とし穴にはまっています。
本当に、created_at
とpublished_at
は本当に同じデータで良いのでしょうか?例えば下書き機能で「下書き追加時点では公開日時は記録しない」などの仕様が発生したら大変です。そしてこの仕様は妥当です。updated_at
とedited_at
も同じで良いのでしょうか。例えばコメントが追加されたり、トラックバックが送信された場合にはどうすれば良いのか困ってしまいます。
(この辺りの設計も、実はイミュータブルデータモデル的に時刻を更新するそれぞれのイベントごとにテーブルを分ければ綺麗に設計できたりするのですが、それは別の話)
つまり、created_at
とupdated_at
はRDBのドメインの上で「レコードが作成された時刻・更新された時刻」以上の意味を持たないことに価値があるのです。RDBドメイン上の情報であるので、RDBの機能(トリガー)でデータを埋めた方が良いとも考えます。
そして、繰り返しとなりますが、アプリケーションから参照するカラム名は、より意味のある命名を心がけましょう。