「基本のキ「簿記のデータモデル」を学ぼう」でイミュータブルデータについて取り上げたが、これに関わる「イベントソーシング」の考え方について説明しよう。理解しておいて損はないモデリングパターンだ。
ただしそれらは、一部で期待されているような「システム開発のややこしさを克服するための設計手法」として一般化できるようなものではない。そのような対応方法に見合う特殊な要件が一部に存在するという、どちらかというと局所的なノウハウでしかない。この種のパターンをナントカのひとつ覚え的に適用すれば、技術者の成長はかえって妨げられ、システム仕様はカオスに落ち込む。そのことを理解したうえで、賢く活用してほしい。
まずはイミュータブルデータについておさらいしよう。「基幹システムが劣化するとデータ分析が栄える」で説明した「ファクト」がその典型例である。すなわち、受払実績(在庫有高増減履歴)、売上実績(売掛増減履歴)、仕入実績(買掛増減履歴)、製造実績(製造原価履歴)といった履歴系のトランザクションデータだ。これらは何らかの取引の淡々とした記録であって、immutable(不変の)の名にふさわしく、基本的に更新されることがない。
イベントソーシングとは
イミュータブルデータには、関連する管理テーブルが1個か複数個存在し、それらの関係においてイベントソーシングの考え方が適用される。よくある例として、マスターテーブル(ホニャララ)とその更新履歴の関係で説明しよう(図1)。各方式の特徴を強調するために、ホニャララを参照するテーブルが1個(ホゲホゲ)が置いてある。

イベントソーシング方式でのホニャララ上には、マスター項目として属性1、属性2が存在し、それらの更新履歴(イミュータブルデータ)が記録されている。ホニャララ上にもそれらがカッコ付きで置かれている点に注意してほしい。それらは「導出属性」であって、ここではホニャララを読み込んだときに、関連する更新履歴を読み込むことで動的に算出される項目、という位置づけである。
このように、あるイミュータブルデータをイベントとする何らかの管理データが存在し、前者を読み込むことで後者上の最新状態を導出できる場合、この設計パターンをイベントソーシングと呼ぶ。ふつうは「イベント履歴から管理データの最新状況を得る手法」と説明されるが、「イベント履歴から管理データ上の導出属性の値を得る手法」と一般化してよい。
これと対立する考え方が「ステートソーシング」である。図1で示したように、ステートソーシングでは更新履歴が存在せず、属性1、属性2の最新値だけがホニャララ上で物理的に保持されている。ホゲホゲが追加されたタイミングにおける属性1、属性2の値は、ホゲホゲ上にそれらをスナップショット属性として置けばわかる。しかし、ホニャララ上の値としてどう変化していったかは復元できない(ホゲホゲが追加されるタイミング以外で更新されることがあり得るからだ)。いっぽうイベントソーシングでは、どのように値が変化したかが逐一記録されているし、ホゲホゲが登録されたタイミングと更新履歴を突き合わすことで、その時点でのマスター属性値を特定できる。
参考までに、更新履歴を扱う別のパターン「履歴内包方式」も見ておこう。ホニャララ自身が更新履歴を兼ねるという一風変わったスタイルで、ホストの時代に見かけたものだ。ホニャララの主キーが{ホニャララID+履歴行番}なので、これを参照するホゲホゲ上にも外部キーとして{ホニャララID+履歴行番}が必要になる。ただし、ホゲホゲからホニャララの最新値を知りたい場合は、最新の履歴行番をスキャンして得る必要がある。なんとなく据わりの悪い仕様に思えないだろうか。
ちなみに、デジタル庁による自治体システムの標準化仕様はもっと奇妙で、なぜかホゲホゲ側に履歴行番は置かれていないので、両者間の参照関係が成立しない。しかも、ホニャララに「最新フラグ」が置かれているので、最新の履歴上に最新値(現時点での有効値)があるわけではない。これは事前に将来の値を登録したい場合の措置らしいのだが、履歴内包方式になっていないテーブルにも「最新フラグ」が置かれているのが解せない。ようするに履歴内包方式は、システムに余計な複雑さを持ち込むアンチパターンと言わざるを得ない。
「導出属性」をどう実装するか
さて、イベントソーシングに関して私が指摘したいのは、履歴内包方式に対する有意性ではない。DB実装においてホニャララ上の導出属性を「実(物理)フィールド」とするか「論理フィールド」とするかの判断に関して偏向している点を取り上げたい。
すべての導出属性を物理化するとしたら、イベントが追加される都度、ホニャララを更新する必要がある。いっぽう、すべてを論理フィールドとして扱うには、ホニャララを読むたびに分析ロジックが実行されるので、一覧表示等における読込操作が重たくなる。私としては、最初は論理フィールドとして置いて、実データの格納状況を見ながら適宜に物理化するやり方をお勧めしたいが、たいていは設計段階でどちらにすべきかを推定できるものだ。
ところが、イベントソーシングではそれらを論理フィールドとするやり方、すなわちホニャララの読込時にそれらの値を更新履歴から動的に得るやり方にこだわる。なぜか。イベントソーシングを有望なデザインパターンとみなすDDD界隈では、仕様を複雑にするとしてUPDATE操作が忌避されるからだ。そもそもDDDでイミュータブルデータが注目されたのもUPDATEが要らないからで、イベントソーシングもその文脈に置かれた手法である。
ホニャララやホゲホゲではわかりにくいだろうから、具体例で説明しよう(図2)。図1ではイミュータブルデータはマスターの更新履歴であった。いっぽう、図2でのイミュータブルデータ「在庫増減履歴(8)」は、さまざまな取引管理簿(1~7)に対する操作にともなって生み出され、これがさまざまな残高管理簿(9~11)の集計ネタになっている。複雑に見えるが、基本的には図1を発展させたパターンでしかないし、在庫管理サブシステムとしてはありきたりなものだ。

残高管理簿(9~11)に置かれた導出属性はどのように実装されるのか。一部は実フィールドとして、一部は論理フィールドとして定義される。その判断はデータモデルが確立された時点でほぼ決まる。
倉庫在庫(9)上の項目を使って説明しよう。期間入庫数、期間出庫数、期間入庫額、期間出庫額、の4項目は本来は導出属性なのだが、論理モデルの段階で実フィールドにされている。いっぽう、現在庫数、現在庫額、現在庫単価の3項目はカッコ付きなので論理フィールドとされていることがわかる。
前者4項目が実フィールドとされているのはなぜか。それらの読み込み操作のレスポンスを確保するためだ。ゆえにそれらは在庫増減履歴が追加された時点で、倉庫在庫に対する(DDD派が嫌う)UPDATE操作によって設定される。もしそれらが論理フィールドにされたら、倉庫在庫を一覧する際に在庫増減履歴が大量に読み込まれるため動作が重くなってしまう。また、それらが月次繰越(*1)をともなう実フィールドであるゆえに、在庫増減履歴のアーカイブ方針にも自由裁量が与えられる。
では、後者3項目が論理フィールドで済むのはなぜか。現在庫数は「期首在庫数-期間入庫数+期間出庫数」で、現在庫額は「期首在庫額-期間入庫額+期間出庫額」で、現在庫単価は「現在庫額÷現在庫数」で得られるからだ。テーブル上の実フィールドを組み合わせるだけなので、倉庫在庫レコードが大量にあっても一覧操作に余計なオーバーヘッドはかからない。
ようするに、よほど単純なシステム要件でない限り、「UPDATEを避けるために、導出可能な項目はすべて論理フィールドにすればいい」という設計方針は通用しないということだ。項目のDB上の位置づけに従って、ケースバイケースで悩んだうえで仕様を決める。それが、高度かつ重責を伴うシステム設計というものである。
コードベース開発の限界
私はこれまでこの設計パターンを表す用語があるとは思っていなかったので、イベントソーシングの言葉を知って素直にうれしかった。しかしながら、それがDDD界隈でUPDATEを避けるためのノウハウとみなされている点にはがっかりさせられる。その応用形として更新系クラスと読取系クラスを分けるCQRS(コマンドクエリ責務分離)なんて大層な手法まで提案されている。UPDATEを避けたがるこだわりは、けっきょく他の何かをややこしくすることでしか成就しない。履歴内包方式がそうであったように。
ではDDDでは、なぜそこまでUPDATEを避ける設計にこだわるのか。オブジェクト指向言語を用いたコードベース開発が前提になっているからだ。必然的に「実装しやすいクラス構造」が求められる。これが話をややこしくする。
業務システムのDB構造(帳簿組織)というものは、「データ要件が項目間の論理構造に変換されたもの」に他ならない。実装しやすさのために手心を加えるとしても、最小限に抑えるべきだ。実装しやすいクラス構造に合わせてDB構造を大幅に改変せざるを得ないとすれば、コードベース開発そのものに疑問を持ったほうがいい。すなわち、UPDATE操作を含んでも仕様が複雑化せず、関数従属性にしたがって素直にDB設計できる「ドメイン特化基盤(ローコード基盤)」のような仕掛け自体をオブジェクト指向開発する。そんな創造的な選択肢があってもいい。
私にとってはイミュータブルデータもイベントソーシングも、昔からやっていた下世話な設計パターンのひとつにすぎない。DDD派がステートソーシングを批判しようが、それでまかなえるケースもふつうにある。メリットとデメリットを勘案しつつ、そしてUPDATEも毛嫌いせず、必要に応じて淡々と活用してほしい。
*1.期首在庫数と期首在庫額は、月次締めの繰越計算で算出される。在庫、買掛、売掛といった情報には月次の会計報告が必要なので、月次締めの対象になる。こういった基礎知識を得るためにも簿記を学んでほしい。