DDD+CQRSで実装すると業務知識がドメインサービスにばかりいってしまう(気がする)

#はじめに
DDD+CQRSで実装すると業務知識がドメインサービスに偏ってしまう気がするんですけど合ってますかね。
具体例を考えながら検討してみました。

#前提
[ここ][間違いだらけの利益率・原価率の計算]に記載されているような、**原価**、**利益率**、**売値**についての計算を行える**商品**という集約を考えます。
**利益率**は商品や年度によって変わるのでインターフェースを用意して交換できるようにします。(**I利益率を取得する**)
UMLっぽく書くと下図のイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109302/7c9705fd-2e4c-96fc-1202-46aaef157401.png)

<!--
@startuml
abstract class 値オブジェクト
class 商品ID << (V,#FF7700) >> {
+ Create(GUID): 商品ID {static}
}
class 原価 << (V,#FF7700) >> {
+ Create(double): 原価 {static}
}
class 利益率 << (V,#FF7700) >> {
+ Create(double): 利益率 {static}
}
class 売値 << (V,#FF7700) >> {
+ Create(double): 売値 {static}
}
商品ID --|> 値オブジェクト
原価 --|> 値オブジェクト
利益率 --|> 値オブジェクト
売値 --|> 値オブジェクト

class 商品 <<Aggrigate>> {
- 商品ID
- 原価
- 利益率
- 売値
}
商品ID -down-o 商品
原価 -down-o 商品
利益率 -down-o 商品
売値 -down-o 商品

interface I利益率を取得する {
+ 取得(商品ID): 利益率
}
class 利益率を取得する {
+ 取得(商品ID): 利益率
}
I利益率を取得する <|-- 利益率を取得する
@enduml
-->

このドメインに、利益率計算の業務知識をどこに実装するべきかを検討します。
計算方法は[ここ][間違いだらけの利益率・原価率の計算]参照。

#エンティティに実装する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109302/7dd2cbdf-e68e-112b-1e52-2a5ff2dd5f41.png)


<!--
@startuml
abstract class 値オブジェクト
class 商品ID << (V,#FF7700) >> {
+ Create(GUID): 商品ID {static}
}
class 原価 << (V,#FF7700) >> {
+ Create(double): 原価 {static}
}
class 利益率 << (V,#FF7700) >> {
+ Create(double): 利益率 {static}
}
class 売値 << (V,#FF7700) >> {
+ Create(double): 売値 {static}
}
商品ID --|> 値オブジェクト
原価 --|> 値オブジェクト
利益率 --|> 値オブジェクト
売値 --|> 値オブジェクト

interface I利益率を取得する {
+ 取得(商品ID): 利益率
}
class 利益率を取得する {
+ 取得(商品ID): 利益率
}
I利益率を取得する <|-- 利益率を取得する

class 商品 <<Aggrigate>> {
- 商品ID
- 原価
- 利益率
- 売値
- I利益率を取得する
+ Get原価
+ Get利益率
+ Get売値
+ Create(I利益率を取得する, 商品ID, 原価): 商品 {static}
# 売値を計算する(原価, 利益率): 売値
}
商品ID -down-o 商品
原価 -down-o 商品
利益率 -down-o 商品
売値 -down-o 商品

interface I商品ファクトリ {
+ Create(原価): 商品
}
class 商品ファクトリ {
- I利益率を取得する
+ 商品ファクトリ(I利益率を取得する)
+ Create(原価): 商品
}
I商品ファクトリ <|-- 商品ファクトリ
商品 <-- 商品ファクトリ : <<create>>
商品ファクトリ <-- I利益率を取得する : <<注入>>
@enduml
-->

上図のように**商品ファクトリ**を介して**商品**を生成します。その際に必要なドメインサービスを注入します。
売値等の取得はエンティティを介して行います。
**売値を計算する**が実際の処理の置き場になります。
しかしエンティティ内に記述してしまうと、別の場所で利益率計算をしたくなったときに困ります。
たとえば利益率がn%だったときの原価と売値の一覧を画面に表示したいといった場合、
CQRS的なクエリでエンティティを介さずDTOに包んで値を取得したときに利益率計算をしようと思っても出来ません。

#ドメインサービスに実装する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109302/9314ed1e-d1eb-143e-48c0-c6e47335bfd3.png)

<!--
@startuml
abstract class 値オブジェクト
class 商品ID << (V,#FF7700) >> {
+ Create(GUID): 商品ID {static}
}
class 原価 << (V,#FF7700) >> {
+ Create(double): 原価 {static}
}
class 利益率 << (V,#FF7700) >> {
+ Create(double): 利益率 {static}
}
class 売値 << (V,#FF7700) >> {
+ Create(double): 売値 {static}
}
商品ID --|> 値オブジェクト
原価 --|> 値オブジェクト
利益率 --|> 値オブジェクト
売値 --|> 値オブジェクト

interface I利益率を取得する {
+ 取得(商品ID): 利益率
}
class 利益率を取得する {
+ 取得(商品ID): 利益率
}
I利益率を取得する <|-- 利益率を取得する

interface I利益率計算 {
+ 計算する(原価, 利益率): 売値
+ 計算する(売値, 利益率): 原価
+ 計算する(原価, 売値): 利益率
}

class 利益率計算 {
+ 計算する(原価, 利益率): 売値
+ 計算する(売値, 利益率): 原価
+ 計算する(原価, 売値): 利益率
}
I利益率計算 <|-- 利益率計算

class 商品 <<Aggrigate>> {
- 商品ID
- 原価
- 利益率
- 売値
- I利益率を取得する
- I利益率計算
+ Get原価
+ Get利益率
+ Get売値
+ Create(I利益率を取得する, I利益率計算, 商品ID, 原価): 商品 {static}
}
商品ID -down-o 商品
原価 -down-o 商品
利益率 -down-o 商品
売値 -down-o 商品

interface I商品ファクトリ {
+ Create(原価): 商品
}
class 商品ファクトリ {
- I利益率を取得する
- I利益率計算
+ 商品ファクトリ(I利益率を取得する, I利益率計算)
+ Create(原価): 商品
}
I商品ファクトリ <|-- 商品ファクトリ
商品 <-- 商品ファクトリ : <<create>>
商品ファクトリ <-- I利益率を取得する : <<注入>>
商品ファクトリ <-- I利益率計算 : <<注入>>
@enduml
-->

上図のように利益率計算用のインターフェースを用意して(1インターフェース1メソッドでも良いかも)、**商品ファクトリ**に、**I利益率計算**と**I利益率を取得する**を注入します。
こうすればエンティティでも利益率計算できますし、DTOで情報を手に入れた場所でも**I利益率計算**を注入すれば使用できます。
ただ、それならわざわざエンティティにドメインサービスを注入せずDTOの時と同じで必要な時に**I利益率計算**を注入して使用すれば良いんじゃないかとも思います。



#値オブジェクトに実装する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109302/a2e48ee6-d136-5556-7700-b6d70ced03fb.png)


<!--
@startuml
abstract class 値オブジェクト
class 商品ID << (V,#FF7700) >> {
+ Create(GUID): 商品ID {static}
}
class 原価 << (V,#FF7700) >> {
+ Create(double): 原価 {static}
+ 売値計算(利益率): 売値
+ 利益率計算(売値): 利益率
}
class 利益率 << (V,#FF7700) >> {
+ Create(double): 利益率 {static}
+ 売値計算(原価): 売値
+ 原価計算(売値): 原価
}
class 売値 << (V,#FF7700) >> {
+ Create(double): 売値 {static}
+ 原価計算(利益率): 原価
+ 利益率計算(原価): 利益率
}
商品ID --|> 値オブジェクト
原価 --|> 値オブジェクト
利益率 --|> 値オブジェクト
売値 --|> 値オブジェクト

interface I利益率を取得する {
+ 取得(商品ID): 利益率
}
class 利益率を取得する {
+ 取得(商品ID): 利益率
}
I利益率を取得する <|-- 利益率を取得する

class 商品 <<Aggrigate>> {
- 商品ID
- 原価
- 利益率
- 売値
+ Get原価
+ Get利益率
+ Get売値
+ Create(商品ID, 原価): 商品 {static}
}
商品ID -down-o 商品
原価 -down-o 商品
利益率 -down-o 商品
売値 -down-o 商品
@enduml
-->

別パターンとして値オブジェクトに実装することも検討してみました。こうすればDTOで情報を取得した際にも利益率計算はできます。
しかしこれだと、もし利益率計算の計算式が変更になった場合とか、差替えが必要になった場合に大変です。
(インターフェースで実装していれば実処理の差替えは楽。値オブジェクトに注入するのは大変([ここ](https://qiita.com/kwhrkzk/items/250d41301b5f469fe072)で考察))

#まとめ
上記のようにエンティティ外でも使用する業務知識はドメインサービスに実装するしかないのかなと。
エンティティ内には

+ クエリ系で使用しない
+ 自分の情報を用いて算出できる

ような業務知識しか置けない気がします。

ドメインサービスもドメインのうちだからドメインモデル貧血症ではないのかな？

[間違いだらけの利益率・原価率の計算]:https://cpasakata.com/riekiritsu-genkaritsu/