本章では、完全性と整合性をモデルで保証する方法を扱います。
今回の内容は、関数型言語に限らず、普段の開発で役立つ内容になってます。
完全性(Integrity)
完全性とは、それぞれのデータが正しいビジネスルールに従っていることを意味します。
(例)
- UnitQuantity〈ユニット数〉は、1 から 1000 の間であるとしました。そのことはコードの中で 何度もチェックしなければならないのでしょうか、それとも常に真であることを当てにできるの でしょうか?
- 注文は常に、明細行が少なくとも 1 つ必要です。
- 注文は、発送部門に送られる前に、発送先住所を検証しておかなければなりません。
整合性(Consistency)
整合性とは、ドメインモデルの異なる部分が事実について一致していることを意味します。
(例)
- 注文の合計金額は、個々の行の合計と同じでなければならない
- 注文が確定すると、それに対応する請求書が作成されなければならない
- 注文時に割引クーポンコードを使用した場合、そのクーポンコードを使用済みとしてマークし、 再使用できないようにする必要がある
6.1 単純な値の完全性
UnitQuantity〈ユニット数〉は、1 から 1000 の間である
スマートコンストラクタで値に対する制約を表現します。
スマートコンストラクタという機能がF#に存在するわけではなく、これは手法です。
6.2 測定単位
F#で用意されている測定単位([<Measure>]
)というアノテーションを使うことで数値の単位に対する制約を表現できます。
6.3 型システムによる不変条件の強制
1 つの注文には必ず少なくとも 1 つの明細行が存在しなければならない
NonEmptyList
という型を定義し、普遍条件を保証します。
6.4 ビジネスルールを型システムで表現する
ビジネスルールを型システムだけで文書化 できるかどうか、つまり、F#の型システムを使って、有効または無効の状態を表現し、ルールが 維持されているかどうかを実行時チェックやコードコメントに頼るのではなく、コンパイラーがチェッ クできるようにしたい。
電子メールが検証済みか未検証かを表現したい
フラグでも表現できますが、間違えて検証済みになる可能性があります。
検証済み電子メールと、未検証電子メールを定義し、電子メールはそれらの選択型とします。
さらに、検証済み電子メールのコンストラクタはprivate
にして、検証サービスだけが検証済み電子メールを作れるようにします。
6.5 整合性
6.5.1 1 つの集約内での整合性
注文と注文明細との整合性
集約ルートである注文で更新を行うことで、集約内での整合性を保ちます。
6.5.2 異なるコンテキスト間の整合性
注文が確定すると、それに対応する請求書が作成されなければなりません。注文は存在するが請求書が存在しない場合、データは矛盾しています。
請求ドメインにメッセージ(またはイベント)を送信し、残りの注文処理を続行するとします。
しかし、メッセージが失われ、請求書が作成されなかった場合どうすれば良いでしょうか?
- 何もしない
- メッセージが失われたことを検出して再送信する
- 前のアクションを「元に戻す」またはエラーを修正する補償アクションを作 成する
6.5.3 同じコンテキストの集約間の整合性
2 つの口座間の送金で、一方の口座が増加し、もう一方の口座が減少する場合
影響を受けるすべてのエンティティをトランザクションに含めれば良いでしょう。
さらに、トランザクション自体をエンティティとしてモデル化するということも考えられます。
こうすることにより、トランザクションが失敗した場合でも、リトライをすることが容易になります。
6.5.4 同一データに作用する複数の集約
Account 集約と MoneyTransfer 集約があり、どちらもアカウントの残高 に作用し、どちらも残高がマイナスにならないようにする必要がある
この制約を実現する方法として、以下のようのものが考えられます。
- NonNegativeMoney〈非負の金額〉型で残高をモデル
化 - 検証関数を共有する