はじめに
業務でドメインサービスを扱う場面が出てきたので、改めて記事にして整理していきます。書籍等ではJavaでの実装が多いと思いますが、今回はLaravelで実装しているので、Laravelを普段使用している人には分かりやすい内容になっているのではないかと思います
ドメインサービスとは
エリック・エヴァンスは著書『エリック・エヴァンスのドメイン駆動設計』において、ドメインサービスを下記のように記載しています。
ドメインから生まれる概念の中には、オブジェクトとしてモデル化すると不自然なものもある。こうしたドメインで必要な機能をエンティティや値オブジェクトの責務として押しつけると、モデルに基づくオブジェクトの定義を歪めるか、意味のない不自然なオブジェクトを追加することになる。
(中略)
ドメインにおける重要なプロセスや変換処理が、エンティティや値オブジェクトの自然な責務でない場合、その操作は、サービスとして宣言される独立したインターフェースとしてモデルに追加すること。モデルの言語を用いてインターフェースを定義し、操作名が必ずユビキタス言語の一部になるようにすること。サービスには状態をもたせないこと。1 )1 )エリック・エヴァンス著.エリック・エヴァンスのドメイン駆動設計,翔泳社,2011,p.104,105
これらをもとに、あえて自分なりに簡単に説明すると、ドメインサービスとは 「重要なドメインの知識であるが、エンティティや値オブジェクトとして表現すると不自然になるとき、その解決策として使用されるパターンのこと」 だと解釈しました。
つまり、設計中に、
🤔「エンティティでも値オブジェクトでもなんかしっくりこないな〜」
😙「ドメインサービスを検討してみよう!」
といった感じで考えられればいいのかと思います
ドメインサービスの注意点
注意点として、ドメインサービスの注意点は何でもかんでもドメインサービスにしないということです
ドメインサービスを乱用してしまうと、ドメインモデル貧血症というアンチパターンに陥ってしまいます。あくまでもエンティティと値オブジェクトで表現できないか?ということを第一に考えましょう
Laravelでドメインサービスを実装する
続いて、ドメインサービスをLaraveで実装していきます。いくつか前提条件があるので、まずはここから記載していきます
前提条件
- 今回は「ユーザーを新規登録する」というユースケースを題材に考えていきます
- ユーザーを登録するためには氏名とユーザー名とパスワードが必要で、
- ユーザー名は他のユーザーと重複してはいけない、というルールがあると仮定します
ユーザーエンティティ内で重複をチェックするisDuplicated(user)
というメソッドを定義して使用してもいいように思いますが、自分に対して重複があるのかを問い合わせを行うのは不自然です。そこでドメインサービスを利用することとします
※ 本記事ではドメインサービスをLaravelでどのように実装していくのか?という目的なので、枝葉の細かい部分は省略します
ドメインサービスの実装例
まずはドメインサービスを実装していきます。ドメインサービスではエンティティにも値オブジェクトでも上手く表現できなかったユーザ名の重複処理をメソッドに持たせます
class UserService
{
public function __construct(
private readonly UserRepositoryInterface $userRepository
) {
}
public function IsDuplicated(Username $username): bool
{
$user = $this->userRepository->findByUsername($username);
return $user !== null;
}
}
コンストラクタインジェクションで、ドメインがリポジトリに依存しないようにします。そしてisDuplicatedメソッドでは、findByUsernameでユーザー名をもとにユーザーを探しにいき、もしもこのユーザーが見つかれば重複としてtrueを返し、見つからなければ重複していないとしてfalseを返します
これでユーザーが重複していないかどうかをチェックします
ユースケースの実装
続いて、先ほど実装したドメインサービスを使って、ユースケースを実装していきます
class SaveUserUseCase
{
private readonly UserService $userService;
private readonly UserRepositoryInterface $userRepository;
public function __construct(UserRepositoryInterface $userRepository)
{
$this->userService = new UserService($userRepository);
$this->userRepository = $userRepository;
}
public function execute($input): void
{
$user = new User(
new Name($input->getName()),
new Username($input->getUserName()),
new Password($input->getInputPassword())
);
if ($this->userService->IsDuplicated($user->getUsername())) {
throw new UseCaseException(
'すでに使用されています。別のユーザー名を入力してください',
422
);
}
$this->userRepository->save($user);
}
}
こちらのユースケースを実行すると、まず最初にユーザーを生成します。そして、入力されたユーザー名が重複していないかを確認するために、先ほど実装したUserServiceのisDuplicatedメソッドを呼び出します。結果がtrueであれば重複しているので、例外を出し、処理を中断させます
以上で、ユースケースとドメインサービスのメソッドisDuplicatedの実装が完了です
参考文献
- エリック・エヴァンス ドメイン駆動設計
- ヴォーン・ヴァーノン 実践ドメイン駆動設計
おわりに
これで、「ドメインサービスとはなにか?Laravelでどう実装するのか」は以上です。何かご指摘等あればぜひコメントお願いします