はじめに
本記事は前回の記事の続きになります。DDDをゼロから学びたい方はまず前回の記事からお読みください。
前回の記事: DDD特別夏季講習 / 1時限目:値オブジェクトとエンティティ
今日は値オブジェクトやエンティティと同じく、ドメインオブジェクトを実装する際に用いられる「ドメインサービス」について解説していきたいいと思います。
対象読者
- 職場でドメイン駆動開発を使って開発を行っている方
- これからドメイン駆動開発の導入を検討している方
- エリック・エバンスの本分厚いよー、ぴえん🥺って方
- その他、ドメイン駆動開発ってなにー??って方
ドメインサービス
値オブジェクトとエンティティの表現の限界
まずはじめに説明するのが、ドメインサービスです。
ドメインサービスは、値オブジェクトやエンティティでドメインを表現しようとすると違和感が生じる場合に利用されます。例を挙げて見てみましょう。
class user {
constructor(name, age){
this.name = name
this.age = age
}
checkAge() {
if (this.age < 18) {
throw new Error("18歳未満の方は入場できません")
}
}
}
上記のコードではcheckAgeというメソッドを用意して、ユーザーが18歳未満でないか確認して、もしそうであった場合にはエラーを吐くようになっています。まあ、みなさんがよくご愛用なさっているサービスとかでも、こういった仕様は頻繁に見受けられるかと思います笑
こういったドメインがもつバリデーションは、本来ユースケース層ではなく、上記の例のようにドメイン層で持つべきものです。でないと、ユースケース層でuserを呼び出す度にこのバリデーションの処理を書かなければいけなくなります。こういったドメイン固有のルールの流失は原則として避けるべきものです。
といった感じで、上記のコードは正しいように思われます。ホントに?
では、実際にこのcheckAgeメソッドを呼んでみましょう。
val user = new user("ruirui", 22)
user.checkAge()
ruiruiさんの年齢が18歳未満でないかを確認していますね! 、、、ruiruiさんが。
上記のクラス及びメソッド定義の最大の問題点はここにあります。
これだと、ユーザ自信がユーザの年齢が条件に合致するか否かを判断しているような感じがしてしまいます。クラスに定義されたメソッドというのは、そのオブジェクトの振る舞いを示したものだからです。
ここで今一度、DDDにおける開発の原則に立ち返っていただきたいのですが、DDDとは「現実世界のドメインの知識を、アプリケーションに必要な物にのみ絞り込んだ上で、それをドメインオブジェクトに落とし込む」といった開発手法です。まあ要は、現実世界のドメインを、必要な分だけ取って実装するって感じですね。つまり実装上のドメインの関係性は現実世界に照らし合わされたものである必要があります。
上記の実装は現実の世界のドメインを正確に表しているとは言えませんね。ユーザ自身が、年齢をチェックするわけではありませんからね。
ドメインサービスの強み
じゃあどう実装すればいいんだよ!まあ、勘の良い方ならもうお気づきでしょう。ここでドメインサービスの出番です。
class userDomainService {
constructor(age) {
this.age = age
}
checkAge() {
if (this.age < 18) {
throw new Error("18歳未満の方は入場できません")
}
}
}
val user = new user("ruirui", 22)
new userDomainService(user.age).checkAge() // 実際に年齢のバリデーションの処理が走る
これで先ほどよりコードに違和感がなくなった感じがしませんか??
このようにドメインサービスは、値オブジェクトやエンティティだけで現実世界のドメインオブジェクトの関係性を正しく表現した実装が困難な場合に使われます。
上記のケース以外では、口座間送金などがドメインサービスを使う例としてよくあげられます。
ドメインサービスを使う上での注意点
ドメインサービスは諸刃の剣です。
ドメインサービスは、値オブジェクトやエンティティでは表現しきれない部分の表現を補ってくれるとっても便利な存在ですが、その一方で大きな問題もあります。それはドメインから説明を奪ってしまうことです。これをドメインモデル貧血症と言います(めちゃめちゃ語呂がいいですよね笑)。
理論上、ドメインの持つべきあらゆる振る舞いをドメインサービスで実装することも可能です。例えばuserにchangeName()がある、みたいなものですかね。
ドメインサービスにこういったchangeName()などを持たせると、userドメインからその振る舞いが消えていってしまいます。
これだと、コードを見たときにそのuserクラスがいったい何をするものなのか、見えにくくなってしまいます。
ドメインサービスを使うのは、値オブジェクトやエンティティでオブジェクトを表現できないときのみにするのが良さそうです。
終わりに
いかがでしたでしょうか??ドメインサービスを使うと、値オブジェクトやエンティティではうまく表現しきれないものでも、自然な表現でドメインオブジェクトの実装が可能になります。一方、むやみやたらに使ってしまうと、ドメインクラスから知識を奪いすぎてしまい、「現実世界のドメインをそのまま実装に落とし込む」DDDの思想に完全に反する事態を招いてしまうので注意しましょう。
これで今日の授業は終わりです。前回の記事と今回の記事を読んだあなたは、おそらく基本的なドメインを実装に落とし込めるまでにはなったはずです。ですが、DDDの構成要素はドメインを定義するドメイン層と、
それを呼び出して実行する箇所だけではありません。次は主にDB等とのやりとりを行うレポジトリ層について解説したいと思いますのでお楽しみに。