Imagine a "clean architecture" in which you have two use cases. One of them is CreateCustomerHandler
, and the other SignUpCustomerByGoogleAuthHandler
. So, the SignUpCustomerByGoogleAuthHandler
has to reuse the CreateCustomerHandler
to eventually create a Customer
.
Is it legit to inject CreateCustomerHandler
to SignUpCustomerByGoogleAuthHandler
as dependency? Are there any pitfalls?
I read somewhere that it's not recommended to reuse your use cases, but what to do in that simple case? Extract the whole CreateCustomerHandler
as Application Service and inject it both to the CreateCustomerHandler
and SignUpCustomerByGoogleAuthHandler
use cases?
CreateCustomerHandler
<?php
declare(strict_types=1);
namespace App\Identity\Application\Customer\UseCase\CreateCustomer;
use App\Identity\Application\Customer\CustomerEntityManager;
use App\Identity\Application\Security\PasswordEncoder;
use App\Identity\Domain\Customer\Customer;
use App\Identity\Domain\Customer\Name;
use App\Identity\Domain\Customer\Username;
use App\Identity\Domain\User\Email;
use App\Identity\Domain\User\Password;
final class CreateCustomerHandler
{
private CustomerEntityManager $customerEntityManager;
private PasswordEncoder $passwordEncoder;
public function __construct(CustomerEntityManager $customerEntityManagerByActiveTenant,
PasswordEncoder $passwordEncoder)
{
$this->customerEntityManager = $customerEntityManagerByActiveTenant;
$this->passwordEncoder = $passwordEncoder;
}
public function handle(CreateCustomerCommand $command): Customer
{
$customerId = $this->customerEntityManager->nextId();
$email = new Email($command->email());
$username = new Username($command->username());
$name = new Name($command->firstname(), $command->lastname());
$password = $this->passwordEncoder->encodePassword($command->password());
$password = new Password($password);
$customer = new Customer(
$customerId,
$email,
$password,
$username,
$name,
);
$this->customerEntityManager->create($customer);
return $customer;
}
}
SignInCustomerByGoogleAuthHandler
<?php
declare(strict_types=1);
namespace App\Identity\Application\Customer\UseCase\SignInCustomerByGoogleAuth;
use App\Identity\Application\Customer\CustomerEntityManager;
use App\Identity\Application\Customer\Query\CustomerByGoogleAuthQuery;
use App\Identity\Application\Customer\UseCase\CreateCustomer\CreateCustomerCommand;
use App\Identity\Application\Customer\UseCase\CreateCustomer\CreateCustomerHandler;
use App\Identity\Application\Security\Security;
use App\Identity\Application\User\UseCase\LinkGoogleAuth\GoogleAuth;
use App\Identity\Domain\Customer\Customer;
final class SignInCustomerByGoogleAuthHandler
{
private CustomerByGoogleAuthQuery $customerByGoogleAuthQuery;
private GoogleAuth $googleAuth;
private CreateCustomerHandler $createCustomerHandler;
private CustomerEntityManager $customerEntityManager;
private Security $security;
public function __construct(CustomerByGoogleAuthQuery $customerByGoogleAuthQuery,
CreateCustomerHandler $createCustomerHandler,
CustomerEntityManager $customerEntityManager,
Security $security,
GoogleAuth $googleAuth)
{
$this->customerByGoogleAuthQuery = $customerByGoogleAuthQuery;
$this->googleAuth = $googleAuth;
$this->createCustomerHandler = $createCustomerHandler;
$this->customerEntityManager = $customerEntityManager;
$this->security = $security;
}
public function handle(SignInCustomerByGoogleAuthCommand $command): Customer
{
$code = $command->code();
$oauthUser = $this->googleAuth->userByOneTimeCode($code);
$customer = $this->customerByGoogleAuthQuery->queryByGoogleUser($oauthUser);
$username = $this->customerEntityManager->nextId()->value();
$password = $this->security->randomPassword();
if (!$customer) {
$command = new CreateCustomerCommand(
$oauthUser->email(),
$username,
$password->hash(),
$oauthUser->firstname(),
$oauthUser->lastname(),
);
$customer = $this->createCustomerHandler->handle($command);
}
// TODO $customer->allowAuthVendor();
return $customer;
}
}
$customer = new Customer...
? You might find your answer to the above question to be insightful – user3347715 May 28 '21 at 15:21Customer
that doesn't require coupling use-cases (and therefore setting a precedent for unbounded data access operations -- i.e. "nested transactions")? You see, I know the answer to your question. But I think you know the answer as well. – user3347715 Jun 01 '21 at 16:30Customer
. Importantly though, we want to make sure the portion of the logic we are abstracting doesn't touch ourCustomerEntityManager
. It's best to keep the outer-most layer of our system as "flat" as possible so that it is easy to glean precisely what's happening when we read our use-case. Yes, that means duplicating the logic to persist ourCustomer
! That makes sense considering you have 2 use-cases that create one though doesn't it? – user3347715 Jun 03 '21 at 14:40