第6回の感想です。前回はこちら。
会社のブログのレポートはこちら。
6章 データベースの設計とドメインオブジェクト
- データベースには事実を記録する。事実にはNULLなどない。基本的にNOT NULL制約を付けなければならない。これは中国地方DB勉強会に参加したときだったと思うが、そう言われたので納得しており、それ以降はNOT NULL制約を必ず付けるようにしている。レビューのときにも「NOT NULL制約を付けろおじさん」になっている。
- NULLが存在していいのは、外部結合(OUTER JOIN)や集約のときくらいなもの。文字列の場合は、デフォルト空文字でいいのでNOT NULLにする。
- データの整理に失敗しているデータベース、めちゃめちゃ見に覚えがある…。今だったら絶対にこんなテーブル設計にしないと自信を持って言えるのだが、昔はそこまでデータベースの知識がなかったので、酷い設計をしていた。現在進行形で苦しんでいる。少しずつテーブル含めてリファクタリングしているが、既に稼働しているシステムのリファクタリングになるので、変更の影響度を調査しながらの修正になるので、修正にかかるコストが大きい。最初の設計時にレビューを受けたりして、ちゃんと見直しておくことが大事。
- 特にRailsの場合はgem由来のテーブルがあったりして、それが制約が緩々だったり、ドメインオブジェクトになっていなかったりするケースもありそう。本に出てきたテーブルの都合が反映されたコードになっていたり等。
- ここに書いてあるNOT NULL制約、ユニーク制約、外部キー制約などは基本的なものなので、出来る限り使うこと。データベース側の機能のチェック制約やenumなどを使うと、更にデータを守ることができる。
- コトに注目するデータベース設計の話。楽々ERDレッスンの話を思い出しながら読んだ。記録のタイミングが異なるデータはテーブルを分ける、というところ。マジでそれ!と思った。今の仕様がそうなっていなくてかなり辛い。コトを記録するという発想がないと、モノに関連するもので一緒くたに詰め込んでしまおうと考えてしまいがち。ドメインオブジェクトの考え方を持っていたら、せめてドメインオブジェクトと一致するテーブルを作ったりしていたのだろうが、当時はそんなことは知らないのでできなかった。
- コトの記録ではUPDATE文は使うべきではない、というのは、なるほどと思った。取り消しを記録する。元データ、取り消しデータ、新データ。INSERTだけで実現可能。例えば、予約テーブルがあったとして、予約をキャンセルされたら、予約取消テーブルに外部キーとして予約IDを持たせてINSERTするということだろうか。
- カラムの追加はテーブルの追加をする、のところ。これくらい大胆な発想のほうがいいんだろうか。確かにそのほうが副作用がないけれど、テーブルが増えるし、JOINが大変になりそうな気もするんだが…。プログラムへの影響は少ないのはそうだけれど。事実を記録する観点だと、そっちのほうがいいのかな。たしかに途中から追加すると、NOT NULL制約が付けられなかったりする。データの関連付け処理を全て終えた後にNOT NULL制約を改めて付けたりしていたけれど、それだとNOT NULL制約が存在しない期間があるので、気を遣うことがある。
- 状態の参照。コトの記録を徹底すれば、状態の算出は可能。動的に出力するのはパフォーマンス的に辛い場合もあるので、この辺りはRailsだとカウンターキャッシュを使ったり、DBのマテビューを使ったりしている。
- DELETE, INSERTを使ったほうがいいケースの話。ロジックがシンプルになる。記録の同時性に違反するから、という方針を持っておくと、UPDATEで対処するべきデータかどうかの判断に使えるのでよさげ。
- 残高更新は同時でなくてもいい、一か所でなくてもいいという考え方は、柔軟性が生まれる。事実はDBに保存し、状態はKVSに持たせるようにすると良さそう。状態の参照は頻繁に行われると想定すると、KVSのほうが向いている。
- オブジェクトの設計とテーブルの設計。コトを記録するテーブルとドメインオブジェクトがほぼ1対1に対応することがある。しかし、似て非なるものという意識を持っておくべき。ドメインオブジェクトとデータベースのアクセスは、基本的に疎結合にしておいたほうがいい。そうでないと、互いに引っ張られ過ぎる。ドメインオブジェクトには業務ロジックを、データベースには事実の記録を。関心事が異なる。しかしそうなるとやはりRailsだと難しい。RailsのActiveRecordはドメインオブジェクトでもあり、データベースへのアクセス手段でもあるからだ。
- Railsを使うんなら、それを分かったうえで、「それはそれ、これはこれ」と割り切っていくのが、現段階ではよさそう。
雑感
データベースの章なので他の参加者に比べて感想が多くなってしまった😅 みんな、あまり制約に気を遣った経験がなかったとのことだった。最近気を遣い始めたというメンバーもいた。後輩氏は私が「NOT NULL制約を付けろおじさん」になっているのでレビューで散々指摘されてきたことと、去年、楽々ERDレッスンを読むように促していたこともあって、この辺りに関してはできるようになってきていると思う。
参加メンバーのプロジェクトでは、状態の導出でパフォーマンスが悪化しているケースがあるということだった。状態テーブルを作ったほうがいいんじゃない?とか、カウンターキャッシュなり、マテビューを使うとかで対処できるよって話をした。
状態に関してはKVSに持たせるとかでもいいと思う、という話をしたのだが、そもそもKVSとは?というメンバーがいたので、そのあたりも説明した。「Redisとか普段使ってるんちゃうの?」と言ったら、「多分使ってると思うんですけど意識して使っているわけではないからほぼ分からないのと同じです」とのことだった。うちのプロジェクトだと、ActiveJobのキューの管理に使っていたりするわけだが、まぁそういう感じで、入れてるけれどActiveJobでよしなにしてるという感じなのだろう。
「まぁでもAWS使ってやっていくんなら、ElasticCacheとか使うことになると思うよ」とか、「Azure使うんでも似たようなものはあるはずだから、その辺り調べて使えるようなら使っていったらパフォーマンス改善するよ」という話をした。「KVS自体が頭になかったので考えもしなかったのですが、たしかに良さそうですね。調べてみます」と言ってもらえた。
UPDATE文を使わないケース、どういう時?という話も出た。予約の取消の場合、取消をお願いした記録が残ってないとマズくない?というケースでディスカッションした。あと、家を建てようとしたときの打ち合わせで決まっていったことは常にINSERTしているなぁと思った、という自分の実体験の話をした。
「こういうテーブル設計について勉強するにはどうしたらいいんですかね~?」と言われたが、後輩氏が「それはやっぱり楽々ERDレッスンを読んでほしいですね」と発言してくれた。頼もしくなってきていて嬉しい限り😂
楽々ERDレッスンの感想記事はこちら。