はじめに
今回はポイント管理システムをテーマに
DDD×クリーンアーキテクチャでサンプルコードを作ったので、
それを使いながら、
ドメインサービスの使いどころと利用時の注意点
についてまとめました。
ドメインサースのサンプルコード
今回テーマにしているポイントシステムでは、
店舗からレシート情報とカード、ポイント利用などを連携され、
付与ポイントの加算、ポイント利用の減算などの
ポイント管理ができるシステムを想定しています。
まずは支払登録ユースケースを実現する際に、
過去に登録したレシート番号を登録しようとしていないかチェックする
ドメインサービスのサンプルコードを見てみましょう。
class ReceiptNumberDuplicationCheckService(
private val paymentRepository: IPaymentRepository
) {
fun execute(
receiptNumber: ReceiptNumber
) {
paymentRepository.findBy(receiptNumber)?.let { throw RuntimeException("Duplicate receipt number") }
}
}
リポジトリを使ってデータベースからレシート番号をkeyに
支払エンティティを取得できたらレシート番号重複エラーで
例外をスローして終わります。
ドメインサービスの使いどころと注意点
まずどういう時にドメインサービスを使うのかについてですが、
ドメインオブジェクトにその業務ロジックを置くと違和感のあるものです。
例えば、
先ほどのコードの例だと
支払エンティティにこのロジックを置けそうな気がしなくもないですが、
レシートIDの重複をチェックするには、
過去に登録した全ての支払エンティティのレシート番号が必要です。
これを、
今回新規で支払登録する
支払エンティティのインスタンスでpayment.dupulicationReceiptNumber()
とするのは違和感がありますよね。。。
こういう時にドメインサービスの利用を検討します。
ただ、
このドメインサービスでは、
手続的で簡単にいろんな業務ロジックを書けるので、
気を抜くとどうしてもいろんなことをやってしまう
神サービスにしてしまいがちです。
まずは、ドメインオブジェクトにロジックの置き場を探して、
違和感がある時だけ最小限の利用を基本方針としましょう。
自分が普段神サービスを避けるために意識しているのは
ドメインサービスを利用する際は特に
クラス名を目的に特化した狭い意味の名前をつけて、
メソッドはexecute()だけにするルールにしています。
例えば、PaymentRegisterクラスなど作ると
支払登録に関するポイント算出ロジック、
整合性チェックなど全てがそこに集まってしまいそうな名前です。
しかし、
今回のサンプルコードのように、
ReceiptNumberDuplicationCheckServiceクラスだと、
レシートの重複チェック以外の処理をここに置くのは
違和感があると思います。
メソッドもexecute()だけなので関係のない業務ロジックが
混ざり辛いです。
命名の重要性についてまとめた記事があるので
興味があればそちらもご覧ください。
命名と小さく分けることの関係と重要性〜ソフトウェア設計のきほん〜今回のテーマについては以上です。
DDD実践のためのきほんシリーズのまとめ記事を作っているので
是非ご覧ください。
初めてドメイン駆動設計を実践する方に向けて〜DDD実践のためのきほん〜参考文献
第6章 ドメイン層の実装
ドメイン駆動設計 モデリング/実装ガイド
Chapter4 不自然さを解決する「ドメインサービス」
ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本