競合イベントは同じテーブルパターン
例えばorder(申込)エンティティが以下の状態遷移をするとします。
※取消(cancel)、完了(complete)のどちらかを持ってライフサイクルが終わります
この時、状態遷移のイベントを残す以下のようなテーブル設計にすると、取消、完了の両方のイベントを記録することができてしまいます。
※PK、FKは主キー、外部キーを表します
取消と完了はどちらか一方のイベントしか許さないので一つのテーブルに記録すると、RDBMSの制約機能により、競合するイベントの登録がブロックされます。
※CHECKは検査制約を表します
イベント固有の情報があるとき
一方のイベント固有のイベントがある場合、カラムを追加するとNULLを許可する必要があるため、以下のように固有情報用のテーブルを作る方法があります。
※FOREIGN KEY(id, order_type)は複合外部キーを表す
検査制約によってorder_cancel_infoのorder_typeは'cancel'しか入らないため、取消時のみ格納できるテーブルになります。よって、「完了イベントなのに取消理由がある」という不整合からカードできます。ただし、「取消のイベントは記録されているのに、取消の情報がない」という不整合を許してしまう課題は残ります(相互参照にしてOracleの制約の遅延評価を使えばもしかして…)
アプリケーションでガードすべきか?
そもそもテーブル設計を頑張らなくてもアプリケーションでガードすればいいという考え方もあると思います。しかし、個人的には処理の一貫性(取消状態から完了に遷移しないなど、正しい状態遷移をする)はアプリケーションで担保、データの一貫性(前述の通り、取消イベントと完了イベントの両方があるなど、正しいデータの状態を保つ)はテーブル設計など、物理データモデルで担保するべきだと思います。