■ 1. はじめに
- 削除フラグの議論:
- 削除フラグを使うかどうかが最近ネットで話題になっている
- とりあえず削除フラグというのはアンチパターンでビジネスドメインでの意味を表現する形にするのが良い
- 記事の目的:
- イベントソーシングでの削除の考え方を紹介する
- すぐにイベントソーシングを導入すべきという話ではない
- イベントソーシングの考え方を知ることで削除の設計について新しい視点が得られることを目指す
■ 2. 前提としてのCQRS
- CQRSとの組み合わせ:
- イベントソーシングを行うときはCQRSと組み合わせて使うことが前提となる
- CQRSは書き込みのためのモデルと読み込みのためのモデルを別々に設計する考え方
- 削除の扱いへの影響:
- この分離により削除の扱いを書き込み側と読み込み側で別々に設計できる
- これがイベントソーシングで削除を扱う上での重要なポイント
■ 3. コマンドモデルでの削除の扱い
- コマンドモデルの役割:
- ビジネスの一貫性を守るために設計する
- 書き込み側のモデルは一貫性確認のための参照にも用いられる
- 無料会員と有料会員を管理する機能では支払い処理を開始した時点ではまだ無料会員で有料機能を使えない
- 銀行振込を確認した時点で有料会員になる
- 削除の意味の多様性:
- ユーザーがいなくなる状況にはさまざまな意味がある
- 支払いが滞った場合はアカウント無効化
- ユーザー自身が退会した場合は退会
- 一時的に休止している場合は休止
- 間違えて作られた場合は誤登録削除
- 規約違反でBANされた場合はアカウント停止
- これらを全て削除フラグでtrueで表現するのはビジネスの意味を失わせる
- イベントによる意味の記録:
- それぞれの意味を持つイベントとして記録する
- 関数型ドメインモデリングの考え方ではイベントによって集約の型が変わることでその状態でできるアクションを制限する
- 型が退会済みになれば有料機能を使うコマンドは型レベルで実行できなくなる
- 削除フラグをチェックする必要がない
- コマンドモデルの設計方針:
- 削除フラグではなくそれぞれの意味を持つイベントを記録し型によって振る舞いを制御する
- 削除フラグ以上の情報を持つ
- 休止や退会により表現する型を変えることでそれぞれのユーザーに対して行うことができるアクションを設計する
■ 4. リードモデルでの削除の扱い
- リードモデルの役割:
- 複数集約の情報をリスト化や集計するために使われる
- コマンドモデルでは一貫性を担保するために集約の単位を適切に設計する必要がある
- 在庫管理で倉庫全体を1集約にするか棚を1集約にするか棚の中の1品目を1集約にするかでトレードオフがある
- 通常は棚の中の1品目を1集約にする設計が良い
- その場合棚全体や倉庫全体での状況を知るためには複数集約の情報をリスト化や集計する必要がある
- 複数ビューの構成:
- 書き込み側で記録されたイベントをもとに目的ごとに異なるリードモデルを構成できる
- 同じ退会というイベントでもリードモデルごとに異なる処理ができる
- 目的に応じた削除の扱い:
- リードモデルでは要件に応じて削除フラグを使うこともできるし物理削除することもできる
- データによって削除時にデータを残すか消すかをコントロールできる
- 要件として削除フラグをリードモデルに置いておけば良いという仕様であれば置くことも可能
- リードモデルの設計特性:
- コマンドモデルと違い型を変えて振る舞いを変えるという設計は通常しない
- データを表示や検索するための情報であり一貫性を型で保証する必要がないから
■ 5. イベントソーシングでの削除の答え
- 基本方針:
- 基本的に削除フラグという汎用的なものは準備しない
- 要件に合わせてコマンドモデルとリードモデルをそれぞれ設計する
- コマンドモデルでの削除:
- 削除がそのビジネスにおいて適切であれば削除としてイベントを記録する
- 多くの場合削除という単一の概念ではなくそれぞれの意味を持つイベントとして設計する
- それぞれの意味を考えてコマンドおよびイベントを設計する
- コマンドモデルが削除フラグ以上の情報を持つ
- 多くの場合は休止や退会により表現する型を変えることでそれぞれのユーザーに対して行うことができるアクションを設計する
- リードモデルでの削除:
- 読み込み側の要望や仕様によって変わる
- 休止や退会を検索条件としたい場合は会員ステータスカラムを作り検索や集計できるようにする
- 有料会員だけを効率的に検索したい場合は休止や退会時にデータを物理削除する
- 退会者の分析をしたい場合は退会者専用のテーブルにデータを移動する
- とりあえず削除を表現したい場合は削除フラグを使っても良い
- 同じイベントから目的に応じて異なる処理ができるのがリードモデルの強み
■ 6. パフォーマンスの観点
- コマンドモデル側のパフォーマンス:
- コマンド側は一貫性とコアな機能に集中しているので各ユーザーが何かの機能を行う時に逐次で呼ばれる
- イベントソーシングでは1集約に対して1つのコマンドしか同時実行できないように設計されている
- アクターモデルなどと一緒に使うことで大きなアクセスでも複数のサーバーに負荷を分散できる
- コマンドモデルは単一集約の処理に集中するため削除されたかどうかのチェックが他のテーブルに波及することはない
- リードモデル側のパフォーマンス:
- 読み込みのパフォーマンスだけを考えて設計できる
- リードモデル生成時に1つのテーブルにアクセスが集中しないように設計できる
- 名称などのマスタ情報を結合したデータを作成できる
- 有料会員専用のテーブルを作れば削除フラグのチェックなしで効率的に検索できる
- 従来のアプローチとの比較:
- 削除フラグを使う従来のアプローチでは多くのクエリにWHERE deleted_flag = falseを追加する必要がありインデックス設計も複雑になる
- リードモデルで有料会員だけのテーブルを作ればこの問題は発生しない
■ 7. イベントソーシングを使わなくても活きる考え方
- 活用可能な考え方:
- イベントソーシングを導入しなくても活きる考え方がある
- 削除を状態遷移として捉え退会や休止や無効化をそれぞれ意味のある状態として設計する
- 書き込みと読み込みで削除の扱いを分けて考えマスタテーブルと検索用ビューで異なる扱いをする
- ビジネスの意味を大切にしたモデリングで削除フラグという技術的な都合ではなくドメインの言葉で表現する
- イベントソーシングの位置づけ:
- これらの考え方を自然に実現できるアーキテクチャの一つ
■ 8. まとめ
- イベントソーシングでの削除の扱い:
- 削除をドメインの言葉で表現する
- コマンドモデルではビジネスの意味を持つイベントとして記録し型で振る舞いを制御し一貫性の担保に焦点を当てる
- リードモデルでは目的に応じて削除フラグや物理削除や別テーブル移動などを選択し検索や表示の効率に焦点を当てる
- イベントソーシングとCQRSの役割:
- DDDを具現化してドメインのビジネス目標や要求や仕様をコードに落とし込むことを支援する
- まずデータモデルの時点で複雑なビジネスを表現し永続化やWebアプリの表示のための加工は副次的なものとして扱う
- 参考になる考え方:
- この考え方はイベントソーシングを導入しなくても参考になる
- 削除フラグをつけるべきかという議論に対して一つの視点を提供する