4

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;
    }
}
Clark
  • 149
  • 4
  • 1
    please don't **[cross-post](https://meta.stackexchange.com/tags/cross-posting/info "'Cross-posting is frowned upon...'")**: https://stackoverflow.com/questions/67671691/best-practice-to-reuse-usecases "Cross-posting is frowned upon as it leads to fragmented answers splattered all over the network..." – gnat May 24 '21 at 11:59
  • 1
    @gnat sorry, wasn't really sure to which community this question is more related. Let's stick to the SO version or this one? – Clark May 24 '21 at 12:01
  • 2
    Please be clear with your terminology. A Use Case is not code, it is a description of functionality the application must have to satisfy a user's need. A handler is code, but depending on whatever your framework is, the nuance of how "Handler" is defined depends on that context. Code questions on Stack Overflow, Design/Process questions here. – Berin Loritsch May 24 '21 at 12:53
  • 1
    @BerinLoritsch by UseCase I mean one of app's scenarios, right. Handler is an implementation of that scenario. So I'm talking abstractly from a code. – Clark May 24 '21 at 13:37
  • 2
    @BerinLoritsch This is yet another unfortunate wording used by R.C.Martin in his "[clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)", which creates a lot of confusion. What he calls "use-cases" correspond more to some transactional logic than to anything related to a real use-case... – Christophe May 24 '21 at 14:22
  • 1
    Included file sources in the message – Clark May 24 '21 at 15:43
  • 4
    Does this answer your question? [Clean architecture - How do I deal with use case reuse?](https://softwareengineering.stackexchange.com/questions/370168/clean-architecture-how-do-i-deal-with-use-case-reuse) – Christophe May 24 '21 at 16:25
  • @Christophe possibly yes. Got also another few discussions (in another place) and have some understanding so we might close the question. – Clark May 27 '21 at 10:53
  • 1
    Why are you choosing to reuse the use-case instead of just `$customer = new Customer...`? You might find your answer to the above question to be insightful – user3347715 May 28 '21 at 15:21
  • @king-side-slide customer creation logic might be not so simple, why to duplicate it? – Clark May 31 '21 at 11:41
  • 1
    @Clark That's a sensible response to my question. But we can do better! Have you thought about _why_ you don't want to duplicate it? Is it because you think the logic might (wait for it) _change_? Do domain objects just appear out of nowhere in your system? Is there another way to encapsulate the logic to create a `Customer` 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:30
  • @king-side-slide well, currently my opinion is that we might extract a common logic to the Application or Domain Service layer and reuse it via DI in both these UseCases (which are actually Application Services). Don't you think so? – Clark Jun 03 '21 at 09:28
  • 1
    @Clark That's exactly right. It could also be a simple as a static factory method defined on `Customer`. Importantly though, we want to make sure the portion of the logic we are abstracting doesn't touch our `CustomerEntityManager`. 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 our `Customer`! That makes sense considering you have 2 use-cases that create one though doesn't it? – user3347715 Jun 03 '21 at 14:40

0 Answers0