I am currently working with DDD and I have a question about application services VS domain services VS repositories interfaces
As I know:
Application services are used for handle the flow of use cases, including any additional concerns needed on top of the domain's.
Domain services are used for encapsulate such behaviors that do not fit in a single domain object.
So, taking account into, for example, this use case:
"when you create a new Car entity(uuid,name) in the system, the car name must be unique (no more cars exist with this name) or the car name can't contains another car name from database as substring",for example, just an example of use case that forces me to take a look into other entities in the repository when I am creating an object
So the question is: Where should I do the checks and/or inject the repository Interface?
- Opt 1) In the application service, injecting the RepositoryCarInterface, do the checks and save the Car:
class CreateCarApplicationService
{
private carRepositoryInterface $carRepository;
public function __construct(CarRepositoryInterface $carRepository)
{
$this->carRepository = $carRepository;
}
public function __invoke(string $carUuid, string $carName): void
{
$this->ensureCarNameIsUnique($CarName);
$car = new Car($carUuid,$carName);
$this->carRepository->save($car);
}
private function ensureCarNameIsUnique(string $carName): void
{
$CarSameName = $this->carRepository->findOneByCriteria(['name' => $carName]);
if ($carSameName) {
throw ExceptionCarExists();
}
}
}
- Opt 2) Create this logic into a domain service (with the purpose of keep the domain logic near to the domain objects) and invoke it from a more simple application service which has the final responsibility of saving the model interacting with database:
class CreateCarDomainService
{
private carRepositoryInterface $carRepository;
public function __construct(carRepositoryInterface $carRepository)
{
$this->carRepository = $carRepository;
}
public function __invoke(string $carUuid, string $carName): Car
{
$this->ensureCarNameIsUnique($CarName);
return new Car($carUuid,$carName);
}
private function ensureCarNameIsUnique(string $carName): void
{
$CarSameName = $this->carRepository->findOneByCriteria(['name' => $carName]);
if ($carSameName) {
throw ExceptionCarExists();
}
}
}
class CreateCarApplicationService
{
private carRepositoryInterface $carRepository;
private CreateCarDomainService $createCarDomainService;
public function __construct(CarRepositoryInterface $carRepository)
{
$this->carRepository = $carRepository;
$this->createCarDomainService = new CreateCarDomainService($carRepository)
}
public function __invoke(string $carUuid, string $carName): void
{
$car = $this->createCarDomainService($carUuid,$carName);
$this->carRepository->save($car);
}
}
I am not very sure about the fact of injecting repository interfaces into domain services, because of as Evans said:
A good SERVICE has three characteristics:
-The operation relates to a domain concept that is not a natural part of an entity or value object
-The interface is defined in terms of other elements of the domain model
-The operation is stateless
But I want to push my domain logic as deep as I cant
And, as I read in other StackOverflow posts, inject repository in domain object is not allowed/recommended: