ドメインモデルの分類学
Modeling with a Sense of Purpose では、データベース設計で論理と物理の区別が有効であるように、UMLにも目的が異なる複数のモデルレベル(概念、仕様、実装の3つ)の区別が必要だ、と述べている。
概念モデル
実世界における関心のある状況を記述する。
a.k.a ドメインモデル、ビジネスモデル
仕様モデル
ソフトウェアシステムが何をしなければならないか、どのような情報を保持しなければならないか、そしてどのような振る舞いを示さなければならないかを定義する。
実装モデル
コンピューティング環境の全ての制約と限界を考慮して、ソフトウェアがどのように実装されるかを記述する。
Modeling with a Sense of Purposeで書かれた時点では、ドメインモデル=概念モデルだったことが読み取れる。だが、後のドメイン駆動設計ではドメインモデルを設計の基盤とし、コードまで一貫させることを目標にし、意図的にこれらを連続体として扱った。そのため、ドメインモデルという用語は広く普及したものの、概念・仕様・実装のどれを指すのかは曖昧になってしまった。
DDDは実装モデルを作る際に始めて用いられることも多くあり、そうなるとドメインモデルは実装技術、実装プログラミング言語の制約を強く受け、概念モデル(業務理解)との乖離が大きくなる。
そこでDDDによって曖昧になった「ドメインモデル」をModeling with a Sense of Purposeに立ち返って再整理すると以下のようになる。
概念ドメインモデル
現代では以下のような手法が概念ドメインモデルの手法、成果物に該当する。これらはシステムが取り扱うかどうかによらず現状の事業を模式化する。
Domain Storytelling
Big Picture EventStorming
仕様ドメインモデル
仕様ドメインモデルをどうかくかは現在広く流通したものはない。Modeling with a Sense of Purposeで想定されているとおり、クラス図で書いても良いが、オブジェクト指向に強く影響されるため実装の都合が乗りやすい。
関数型ドメインモデリング にあるように、システムで扱うデータと振る舞いをDSLで書き出すのがおすすめである。関数型ドメインモデリングに書かれているミニ言語をベースに記法や注意点を整理したものをドメイン記述ミニ言語に示す。
実装ドメインモデル
仕様ドメインモデルを、実装制約とプログラミング言語の特性を考慮してエンコーディングしたものが実装ドメインモデルである。実装モデルになると当然ながら実装の都合のモデル(PresentationモデルやPersistenceモデルなど)も扱うので、これらと混同しがちになる。
考察
仕様ドメインモデルは必要か?
困るケース①: 仕様ドメインモデルとしては必要のない概念が実装で追加される
例えばメールアドレスがアクティベーションされているかどうかによってメールを送るかどうかが決まる場合を考える。
仕様ドメインモデルとしては以下のように表せる。
code:仕様ドメインモデル.md
- メールアドレスはRFC 5322に沿った文字列である
- メールアドレスは未アクティベートとアクティベート済みのものがある
- アクティベート済みのものだけがメールを送信でできる
ドメイン記述ミニ言語で書くと以下のようになる。
code:仕様ドメインモデル
data メールアドレス = 未アクティベートメールアドレス OR アクティベート済みメールアドレス
behavior メールを送る = アクティベート済みメールアドレス AND メッセージ本文 -> メール送信イベント
実装ドメインモデルになると例えば次のように表すかもしれない。
code:mermaid
classDiagram
class EmailAddress {
String value
boolean isActivated
}
code:java
record EmailAddress(
String value,
boolean isActivated
) {}
そうすると、仕様としては必要ないisActivatdフラグが登場してくる。
困るケース② 仕様上区別される概念が同じ実装にまとめられる
例えば以下のような簡易なEC業務を考える。
注文が確定前であれば、商品の追加や削除ができる。
注文が確定すると、商品の変更はできず、請求額も確定する。
また、確定済みの注文には必ず配送先が設定されていなければならない。注文のキャンセルが可能
発送済みの注文は、追跡番号が付与され、注文のキャンセルは不可能になる。
配達完了になると一切の注文の変更はできない。
ここから直接実装ドメインモデルを作ると次のように表すかもしれない。
code:Order.mmd
classDiagram
class Order {
UUID orderId
Address shippingAddress
Address trackingNumber
int orderStatus
List~OrderLine~ lines
BigDecimal totalAmount
addItem(item)
removeItem(item)
confirm()
ship()
cancel
markDelivered()
reportIssue()
}
orderStatusには確定前注文や確定済み注文、発送済み注文などを表すが、このステータスによって属性の不変条件が異なるし、振る舞いもステータスによっては適用できないものが存在する。これらはコードには正しく実装されるかもしれないが、実装ドメインモデルとしては暗黙的になってしまう。
このように一般的には仕様上区別されるものが、実装ドメインモデルは同じまとまりになることがある。(抽象化にも見えるが、共通のデータ、振る舞いとそうでないものとを明確に区別できていないならばそれは単なるキメラである。)