仕様オブジェクト
仕様はあるオブジェクトがある評価基準に達しているかを判定するオブジェクト。
- オブジェクトの評価処理にはオブジェクトのメソッドとして定義されるには似つかわしくないものが存在する
- 複雑な評価の手順はアプリケーションサービスに記述されてしまうことが多い
- オブジェクトの評価はドメインの重要なルールのため、アプリケーションサービスに記述されることは問題である
良くないとされる例
リポジトリを持つアプリケーション上で評価を行う
アプリケーションサービス.php
class ApplicationService
{
private BookRepository $bookRepository;
public function save(Book $book)
{
$storedBooks = $this->bookRepository->getAll();
foreach ($storedBooks as $storedBook) {
if ($storedBook->name == $book->name) {
throw new Exception('既に登録済みの書籍です');
}
}
$this->bookRepository->save($book);
}
}
既に登録されている名前の書籍は登録できないというルールは、ドメイン知識である。
=>アプリケーションサービスにドメインのルールに基づくロジックを記述すると、ドメインオブジェクトは何も語らず、ドメインの重要なルールはサービスの至る所に記述されるようになってしまう。大前提として、ドメインのルールはドメインオブジェクトに定義されるべき。
エンティティがリポジトリを受け取り、評価を行う
エンティティ.php
class Entity
{
private Book $book;
public function isSave(BookRepository $bookRepository)
{
$storedBooks = $this->bookRepository->getAll();
foreach ($storedBooks as $storedBook) {
if ($storedBook->name == $book->name) {
throw new Exception('既に登録済みの書籍です');
}
}
return $this->book;
}
}
=>リポジトリはそもそもドメイン由来のものではないため、エンティティがドメインモデルの表現に徹していないことになる。エンティティや値オブジェクトがドメインモデルの表現に専念するにはリポジトリを操作することを可能な限り避ける必要がある。
推奨される例
評価はアプリケーションサービスで、仕様クラスを呼び出して行う
仕様クラス.php
class Specification
{
private BookRepository $bookRepository;
public function __construct(BookRepository $bookRepository){
$this->bookRepository = $bookRepository;
}
public function isSatisfied(Book $book) :bool
{
$storedBooks = $this->bookRepository->getAll();
foreach ($storedBooks as $storedBook) {
if ($storedBook->name == $book->name) {
return false;
}
};
return true;
}
}
この仕様クラスをアプリケーションサービスで呼び出して、評価に使う。
アプリケーションサービス.php
class ApplicationService
{
public function __construct(Specification $specification){
$this->specification = $specification;
}
public function save(Book $book)
{
if (!$this->specification) {
throw new Exception('既に登録済みの書籍です');
}
$this->bookRepository->save($book);
}
}
=>仕様はオブジェクトの評価のみを行う。複雑な評価手順をオブジェクトに埋させずに切り出すことで、その趣旨が明確になっている。
もし、オブジェクトの評価処理をオブジェクト自身に実装すると、オブジェクトの趣旨はぼやけてしまい、オブジェクトが何のために存在し何を為すのかが見えづらくなる。