🍥

イミュータブルデータモデルから業務データの世代管理を考える

2022/08/24に公開

はじめに

どんなソフトウェアでも大事となるのが「データベース設計」です。その中でも、データの更新履歴に関する悩みは尽きません。それに対する手法として「イミュータブルデータモデリング」があります。イミュータブルデータモデルは発想自体はシンプルで「重要な事実が失われないようにするために、Updateの発生を削る」という考え方です。
イミュータブルデータモデルについて調べると、RDBでの事例が多く出ます。
しかし今回はあえてFirestoreでも非常に有用と思い、今回の記事を書きました。

イミュータブルデータモデルとは

こちらの記事が超絶わかりやすいので是非読んでみてください!
https://scrapbox.io/kawasima/イミュータブルデータモデル
https://qiita.com/urakawa/items/3d7777e6734cb5c15bd1

注意

イミュータブルデータモデルは物理的なデータベース設計の話だけではなく、エンティティやイベント・リソースの抽出を含んだものです。何が何でもUpdateはやめようという話ではもちろんないので注意。

具体的な例で考えてみる

物件を管理するサービス

1. 入居者と物件の関係を管理したいというケース、何も考えてないバージョン

※緑色がコレクション、黒色がドキュメントを表します

コレクション

確かに誰がどの物件に入居しているかはわかりますが、以下の重要な事実がデータから消えています。

  • 同じ物件に何年住んでいるか
  • 何度契約を更新したか
  • 最初の更新の時は家賃がいくらだったか

2. 「契約イベント」を抽出する

物件と入居者の間には「契約」が存在するはずです。2年更新するたびに新しく契約を結び直すと考えて、あたらしく契約コレクションを作成しました。

コレクション

さっきよりよさそうですね!いつ更新したかの履歴(更新日時)が大事な事実として保存されるようになりました。
これのおかげで同じ物件に何年住んでいるか、何度契約を更新したかなどもデータから失うことがなくなりました。
しかし、物件の名前が変わるとどうでしょうか?契約にはそれぞれのIDしか持っていないので、物件ドキュメントが更新されると、最初の契約では「高木アパート」だったのに、契約履歴が全て「メゾン高木」になってしまいます。

3. 物件の「世代」を抽出する

物件には、名称の変更・リフォーム・家賃の値上げなど様々な変更履歴があるはずです。それを「バージョン」として抽出して、物件のサブコレクションとして作成しました。

コレクション

こうすれば、物件のリフォーム履歴も、入居者が家賃がいくらのときの物件に住んでいたかもデータから失うことがなくなりました!名前が変わっていたとしても、当時の情報を正しく再現することができます。

どうして「世代」をサブコレクションとしているか

今回の例では物件の世代をサブコレクションとしました。しかし、ルートコレクション(物件コレクションと同列)でも同じ設計は可能です。しかし、サブコレクションにすることで以下のメリットがあります。

  • サブコクションの親の履歴であることが明示的である
  • FirestoreのQueryでWhereを一つ減らせる
  • セキュリティルールの利用がしやすい(*これは場合によります)

特に2番目のメリットは大きく、FirestoreのWhereを一つ減らすことができる=複合インデックスを貼らなくて良くなるケースが増えます。

NoSQLではイミュータブルデータモデルの考え方は使えないのか?

イミュータブルデータモデルの考え方は業務やシステムを考える上で非常に有用だと感じました。
イミュータブルデータモデルでの大事なことは「重要な事実が失われないようにするために、Updateの発生を削る」ことです。
サブレコレクションはInsertで履歴を残し、親ドキュメントは最新状態をキープする、といった使い方をすれば変更履歴や重要なイベントを保存することも簡単です。
もちろんFirestoreはドキュメントDBであり、RDBに比べて非正規化する戦略をとることも多く、何が何でもタイムスタンプを1フィールドにして正規化をキープし続けることが良いとも限りません。しかし、スキーマレスであるメリットを活かして、「とりあえずスナップショットを入れとく」という使い方もできます。
(お気づきの方もいると思いますが、今回の例ではステップが進むにつれ、イベントが抽出と同時に正規化されています)

Discussion