実践DDD-ドメインモデル

## はじめに

前回イベントストーミングで業務シナリオを洗い出したので、今回はドメインモデルを作っていきたいと思います。

イベントストーミングがよくわからない方は前回の記事をぜひ
https://qiita.com/ma-ru55/items/81a7ef0cb3320bcba2c7

## イベントストーミングで洗い出したモデル

![スクリーンショット 2024-11-11 22.13.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900459/1c794dad-45ac-dbe5-c39b-4340de4f5035.png)

ドメインモデル描いていたら、モデル図修正した方がよさそうな箇所あったので、前回の記事と少し図が変わってます。

## ドメインモデルを描いてみよう

今回は商品購入コンテキストを例に挙げていきます。
[PlantUML](https://plantuml.com/ja/)を使用していきます。

### 集約

まず、集約を定義していきます。
今回はOrder（注文）をルートエンティティに、OrderItem(注文商品)とPaymentMethod(決済手段)エンティティを持つようにします。

~~~
@startuml OrderAggregation

title 注文集約 (Order Aggregation)

package "注文集約(OrderAggregation)" {

    class "Order(注文)" as Order << (R,red) RootEntity >> {
        OrderId: OrderId
        OrderDateTime: 注文日時
        Status: ステータス
        TotalAmout: 合計
    }

    class "OrderItem(注文商品)" as Item << (E,green) Entity >> {
        ItemId: ItemId
        quantity: 個数
        subtotal: 小計
    }

    class "PaymentMethod(決済手段)" as Item << (E,green) Entity >> {
        PaymentMethodId: PaymentMethodId
        PaymentMethodType: 決済種別
    }
}

@enduml
~~~

~~~plantuml
@startuml OrderAggregation

title 注文集約 (Order Aggregation)

package "注文集約(OrderAggregation)" {

    class "Order(注文)" as Order << (R,red) RootEntity >> {
        OrderId: OrderId
        OrderDateTime: 注文日時
        Status: ステータス
        TotalAmout: 合計
    }

    class "OrderItem(注文商品)" as OrderItem << (E,green) Entity >> {
        ItemId: ItemId
        quantity: 個数
        subtotal: 小計
    }

    class "PaymentMethod(決済手段)" as PaymentMethod << (E,green) Entity >> {
        PaymentMethodId: PaymentMethodId
        PaymentMethodType: 決済種別
    }
}

@enduml
~~~

### 属性

次にエンティティの持つデータや情報を定義していきます。
コンテキスト合わせて名称を決めていきます。

~~~
@startuml OrderAggregation

title 注文集約 (Order Aggregation)

package "注文集約(OrderAggregation)" {

    class "Order(注文)" as Order << (R,red) RootEntity >> {
        OrderId: OrderId
        OrderDateTime: 注文日時
        Status: ステータス
        TotalAmout: 合計
    }

    class "OrderId" as OrderId {
        + id: string
    }

    class "OrderDateTime" as OrderDateTime {
        + datetime: LocalDateTime
    }

    class "Status" as Status {
        + status: Enum { UNORDERED, ORDERED, DELIVERED }
    }

    class "TotalAmout" as TotalAmout {
        + totalAmount: Integer
        + subtotal: SubTotal
        + taxAmount: TaxAmount
        + feeAmount: FeeAmount
    }

    class "TaxAmount" as TaxAmount {
        + amount: Integer
    }

    class "FeeAmount" as FeeAmount {
        + amount: Integer
    }

    class "OrderItem(注文商品)" as Item << (E,green) Entity >> {
        ItemId: ItemId
        quantity: 個数
        subtotal: 小計
    }

    class "ItemId" as ItemId {
        + id: String
    }

    class "Quantity" as Quantity {
        + value: Integer
    }

    class "Subtotal" as Subtotal {
        + amount: Integer
    }


    class "PaymentMethod(決済手段)" as Item << (E,green) Entity >> {
        PaymentMethodId: PaymentMethodId
        PaymentMethodType: 決済種別
    }

    class "PaymentMethodId" as PaymentMethodId {
        + id: String
    } 

    class "PaymentMethodType" as PaymentMethodType {
        + type: Enum { CREDIT_CARD, E_MONEY, BANK_TRANSFER }
    }
}

@enduml
~~~

~~~plantuml
@startuml OrderAggregation

title 注文集約 (Order Aggregation)

package "注文集約(OrderAggregation)" {

    class "Order(注文)" as Order << (R,red) RootEntity >> {
        OrderId: OrderId
        OrderDateTime: 注文日時
        Status: ステータス
        TotalAmout: 合計
    }

    class "OrderId" as OrderId {
        + id: string
    }

    class "OrderDateTime" as OrderDateTime {
        + datetime: LocalDateTime
    }

    class "Status" as Status {
        + status: Enum { UNORDERED, ORDERED, DELIVERED }
    }

    class "TotalAmout" as TotalAmout {
        + totalAmount: Integer
        + subtotal: SubTotal
        + taxAmount: TaxAmount
        + feeAmount: FeeAmount
    }

    class "TaxAmount" as TaxAmount {
        + amount: Integer
    }

    class "FeeAmount" as FeeAmount {
        + amount: Integer
    }

    class "OrderItem(注文商品)" as OrderItem << (E,green) Entity >> {
        ItemId: ItemId
        quantity: 個数
        subtotal: 小計
    }

    class "ItemId" as ItemId {
        + id: String
    }

    class "Quantity" as Quantity {
        + value: Integer
    }

    class "Subtotal" as Subtotal {
        + amount: Integer
    }


    class "PaymentMethod(決済手段)" as PaymentMethod << (E,green) Entity >> {
        PaymentMethodId: PaymentMethodId
        PaymentMethodType: 決済種別
    }

    class "PaymentMethodId" as PaymentMethodId {
        + id: String
    } 

    class "PaymentMethodType" as PaymentMethodType {
        + type: Enum { CREDIT_CARD, E_MONEY, BANK_TRANSFER }
    }
}

@enduml
~~~

今回は教科書的に全て値オブジェクトにしましたが、個人的に制約ないものなどは、値オブジェクトにしても、クラス増えるのに対して、あまりメリットを感じてないので、値オブジェクトでなくていいと思います。

### 制約

次に制約を追加していきます。
ビジネス上の制約を記載していきます。

~~~
@startuml OrderAggregation

title 注文集約 (Order Aggregation)

package "注文集約(OrderAggregation)" {

    class "Order(注文)" as Order << (R,red) RootEntity >> {
        OrderId: OrderId
        OrderDateTime: 注文日時
        Status: ステータス
        TotalAmout: 合計
    }

    class "OrderId" as OrderId {
        + id: string
    }

    note bottom of OrderId
        UUIDを適用する。
    end note

    class "OrderDateTime" as OrderDateTime {
        + datetime: LocalDateTime
    }

    note bottom of OrderDateTime
        日本標準時で定義すること。
    end note

    class "Status" as Status {
        + status: Enum { UNORDERED, ORDERED, DELIVERED }
    }

    note bottom of Status
        初回作成時はUNORDEREDから始まる
        UNORDEREDは未注文
        ORDEREDは注文済み
        DELIVEREDは配達済み
    end note

    class "TotalAmout" as TotalAmout {
        + totalAmount: Integer
        + subtotal: SubTotal
        + taxAmount: TaxAmount
        + feeAmount: FeeAmount
    }

    note bottom of TotalAmout
        日本円で扱う
        MIN=0
    end note

    class "TaxAmount" as TaxAmount {
        + amount: Integer
    }

    note bottom of TaxAmount
        日本円で扱う。
        小数点以下は切り捨て
        MIN=0
    end note

    class "FeeAmount" as FeeAmount {
        + amount: Integer
    }

    note bottom of FeeAmount
        日本円で扱う。
        小数点以下は切り捨て
        MIN=0
    end note

    class "OrderItem(注文商品)" as OrderItem << (E,green) Entity >> {
        ItemId: ItemId
        quantity: 個数
        subtotal: 小計
    }

    class "ItemId" as ItemId {
        + id: String
    }

    note bottom of ItemId
        UUIDを適用する。
    end note

    class "Quantity" as Quantity {
        + value: Integer
    }

    note bottom of Quantity
        MIN=0
    end note

    class "Subtotal" as Subtotal {
        + amount: Integer
    }

    note bottom of Subtotal
        日本円で扱う
        MIN=0
    end note


    class "PaymentMethod(決済手段)" as PaymentMethod << (E,green) Entity >> {
        PaymentMethodId: PaymentMethodId
        PaymentMethodType: 決済種別
    }

    class "PaymentMethodId" as PaymentMethodId {
        + id: String
    } 

    note bottom of PaymentMethodId
        MIN=2
        MAX=2
    end note

    class "PaymentMethodType" as PaymentMethodType {
        + type: Enum { CREDIT_CARD, E_MONEY, BANK_TRANSFER }
    }

    note bottom of PaymentMethodType
        CREDIT_CARDはクレジットカード
        E_MONEYは電子マネー
        BANK_TRANSFERは銀行振り込み
    end note
}

@enduml
~~~

~~~plantuml
@startuml OrderAggregation

title 注文集約 (Order Aggregation)

package "注文集約(OrderAggregation)" {

    class "Order(注文)" as Order << (R,red) RootEntity >> {
        OrderId: OrderId
        OrderDateTime: 注文日時
        Status: ステータス
        TotalAmout: 合計
    }

    class "OrderId" as OrderId {
        + id: string
    }

    note bottom of OrderId
        UUIDを適用する。
    end note

    class "OrderDateTime" as OrderDateTime {
        + datetime: LocalDateTime
    }

    note bottom of OrderDateTime
        日本標準時で定義すること。
    end note

    class "Status" as Status {
        + status: Enum { UNORDERED, ORDERED, DELIVERED }
    }

    note bottom of Status
        初回作成時はUNORDEREDから始まる
        UNORDEREDは未注文
        ORDEREDは注文済み
        DELIVEREDは配達済み
    end note

    class "TotalAmout" as TotalAmout {
        + totalAmount: Integer
        + subtotal: SubTotal
        + taxAmount: TaxAmount
        + feeAmount: FeeAmount
    }

    note bottom of TotalAmout
        日本円で扱う
        MIN=0
    end note

    class "TaxAmount" as TaxAmount {
        + amount: Integer
    }

    note bottom of TaxAmount
        日本円で扱う。
        小数点以下は切り捨て
        MIN=0
    end note

    class "FeeAmount" as FeeAmount {
        + amount: Integer
    }

    note bottom of FeeAmount
        日本円で扱う。
        小数点以下は切り捨て
        MIN=0
    end note

    class "OrderItem(注文商品)" as OrderItem << (E,green) Entity >> {
        ItemId: ItemId
        quantity: 個数
        subtotal: 小計
    }

    class "ItemId" as ItemId {
        + id: String
    }

    note bottom of ItemId
        UUIDを適用する。
    end note

    class "Quantity" as Quantity {
        + value: Integer
    }

    note bottom of Quantity
        MIN=0
    end note

    class "Subtotal" as Subtotal {
        + amount: Integer
    }

    note bottom of Subtotal
        日本円で扱う
        MIN=0
    end note


    class "PaymentMethod(決済手段)" as PaymentMethod << (E,green) Entity >> {
        PaymentMethodId: PaymentMethodId
        PaymentMethodType: 決済種別
    }

    class "PaymentMethodId" as PaymentMethodId {
        + id: String
    } 

    note bottom of PaymentMethodId
        MIN=2
        MAX=2
    end note

    class "PaymentMethodType" as PaymentMethodType {
        + type: Enum { CREDIT_CARD, E_MONEY, BANK_TRANSFER }
    }

    note bottom of PaymentMethodType
        CREDIT_CARDはクレジットカード
        E_MONEYは電子マネー
        BANK_TRANSFERは銀行振り込み
    end note
}

@enduml
~~~

### 関連性

最後に関連性を定義して図を完成させます。
重みも一緒につけます。

~~~
@startuml OrderAggregation

title 注文集約 (Order Aggregation)

package "注文集約(OrderAggregation)" {

    class "Order(注文)" as Order << (R,red) RootEntity >> {
        OrderId: OrderId
        OrderDateTime: 注文日時
        Status: ステータス
        TotalAmout: 合計
    }

    class "OrderId" as OrderId {
        + id: string
    }

    note bottom of OrderId
        UUIDを適用する。
    end note

    class "OrderDateTime" as OrderDateTime {
        + datetime: LocalDateTime
    }

    note bottom of OrderDateTime
        日本標準時で定義すること。
    end note

    class "Status" as Status {
        + status: Enum { UNORDERED, ORDERED, DELIVERED }
    }

    note bottom of Status
        初回作成時はUNORDEREDから始まる
        UNORDEREDは未注文
        ORDEREDは注文済み
        DELIVEREDは配達済み
    end note

    class "TotalAmout" as TotalAmout {
        + totalAmount: Integer
        + subtotal: Subtotal
        + taxAmount: TaxAmount
        + feeAmount: FeeAmount
    }

    note bottom of TotalAmout
        日本円で扱う
        MIN=0
    end note

    class "TaxAmount" as TaxAmount {
        + amount: Integer
    }

    note bottom of TaxAmount
        日本円で扱う。
        小数点以下は切り捨て
        MIN=0
    end note

    class "FeeAmount" as FeeAmount {
        + amount: Integer
    }

    note bottom of FeeAmount
        日本円で扱う。
        小数点以下は切り捨て
        MIN=0
    end note

    class "OrderItem(注文商品)" as OrderItem << (E,green) Entity >> {
        ItemId: ItemId
        quantity: 個数
        subtotal: 小計
    }

    class "ItemId" as ItemId {
        + id: String
    }

    note bottom of ItemId
        UUIDを適用する。
    end note

    class "Quantity" as Quantity {
        + value: Integer
    }

    note bottom of Quantity
        MIN=0
    end note

    class "Subtotal" as Subtotal {
        + amount: Integer
    }

    note bottom of Subtotal
        日本円で扱う
        MIN=0
    end note


    class "PaymentMethod(決済手段)" as PaymentMethod << (E,green) Entity >> {
        PaymentMethodId: PaymentMethodId
        PaymentMethodType: 決済種別
    }

    class "PaymentMethodId" as PaymentMethodId {
        + id: String
    } 

    note bottom of PaymentMethodId
        MIN=2
        MAX=2
    end note

    class "PaymentMethodType" as PaymentMethodType {
        + type: Enum { CREDIT_CARD, E_MONEY, BANK_TRANSFER }
    }

    note bottom of PaymentMethodType
        CREDIT_CARDはクレジットカード
        E_MONEYは電子マネー
        BANK_TRANSFERは銀行振り込み
    end note

    Order "1" -down- "1" OrderItem : has >

    Order "1" -down- "1" PaymentMethod : has >

    Order *-down- OrderId
    Order *-down- OrderDateTime
    Order *-down- Status
    Order *-down- TotalAmout

    TotalAmout *-down- Subtotal
    TotalAmout *-down- TaxAmount
    TotalAmout *-down- FeeAmount

    OrderItem *-down- ItemId
    OrderItem *-down- Subtotal
    OrderItem *-down- Quantity

    PaymentMethod *-down- PaymentMethodId
    PaymentMethod *-down- PaymentMethodType
}



@enduml
~~~

~~~plantuml
@startuml OrderAggregation

title 注文集約 (Order Aggregation)

package "注文集約(OrderAggregation)" {

    class "Order(注文)" as Order << (R,red) RootEntity >> {
        OrderId: OrderId
        OrderDateTime: 注文日時
        Status: ステータス
        TotalAmout: 合計
    }

    class "OrderId" as OrderId {
        + id: string
    }

    note bottom of OrderId
        UUIDを適用する。
    end note

    class "OrderDateTime" as OrderDateTime {
        + datetime: LocalDateTime
    }

    note bottom of OrderDateTime
        日本標準時で定義すること。
    end note

    class "Status" as Status {
        + status: Enum { UNORDERED, ORDERED, DELIVERED }
    }

    note bottom of Status
        初回作成時はUNORDEREDから始まる
        UNORDEREDは未注文
        ORDEREDは注文済み
        DELIVEREDは配達済み
    end note

    class "TotalAmout" as TotalAmout {
        + totalAmount: Integer
        + subtotal: Subtotal
        + taxAmount: TaxAmount
        + feeAmount: FeeAmount
    }

    note bottom of TotalAmout
        日本円で扱う
        MIN=0
    end note

    class "TaxAmount" as TaxAmount {
        + amount: Integer
    }

    note bottom of TaxAmount
        日本円で扱う。
        小数点以下は切り捨て
        MIN=0
    end note

    class "FeeAmount" as FeeAmount {
        + amount: Integer
    }

    note bottom of FeeAmount
        日本円で扱う。
        小数点以下は切り捨て
        MIN=0
    end note

    class "OrderItem(注文商品)" as OrderItem << (E,green) Entity >> {
        ItemId: ItemId
        quantity: 個数
        subtotal: 小計
    }

    class "ItemId" as ItemId {
        + id: String
    }

    note bottom of ItemId
        UUIDを適用する。
    end note

    class "Quantity" as Quantity {
        + value: Integer
    }

    note bottom of Quantity
        MIN=0
    end note

    class "Subtotal" as Subtotal {
        + amount: Integer
    }

    note bottom of Subtotal
        日本円で扱う
        MIN=0
    end note


    class "PaymentMethod(決済手段)" as PaymentMethod << (E,green) Entity >> {
        PaymentMethodId: PaymentMethodId
        PaymentMethodType: 決済種別
    }

    class "PaymentMethodId" as PaymentMethodId {
        + id: String
    } 

    note bottom of PaymentMethodId
        MIN=2
        MAX=2
    end note

    class "PaymentMethodType" as PaymentMethodType {
        + type: Enum { CREDIT_CARD, E_MONEY, BANK_TRANSFER }
    }

    note bottom of PaymentMethodType
        CREDIT_CARDはクレジットカード
        E_MONEYは電子マネー
        BANK_TRANSFERは銀行振り込み
    end note

    Order "1" -down- "1" OrderItem : has >

    Order "1" -down- "1" PaymentMethod : has >

    Order *-down- OrderId
    Order *-down- OrderDateTime
    Order *-down- Status
    Order *-down- TotalAmout

    TotalAmout *-down- Subtotal
    TotalAmout *-down- TaxAmount
    TotalAmout *-down- FeeAmount

    OrderItem *-down- ItemId
    OrderItem *-down- Subtotal
    OrderItem *-down- Quantity

    PaymentMethod *-down- PaymentMethodId
    PaymentMethod *-down- PaymentMethodType
}



@enduml
~~~

### さいごに

今回はドメインモデルのやり方を実践してみました。
こうやって図にしてみると、どういうクラスを用意して、どういうチェックをするかというのが、だいぶイメージつくのではないのでしょうか。

次回は実際にソースにマッピングさせていきます。