jorge07 / symfony-6-es-cqrs-boilerplate

Symfony 6 DDD ES CQRS backend boilerplate.
MIT License
1.07k stars 187 forks source link

Unique email validation #96

Closed ferrius closed 5 years ago

ferrius commented 5 years ago

First of all, thank you for a great example!

We have an aggregate domain rule "user must have an unique email", but we have just one assertion in factory. This is not a defensive way. In DDD domain object is permanently valid and can be used in stand alone manner. You can use specification pattern for validating aggregate rules.

public function changeEmail(Email $email, UniqueEmailSpecification $uniqueEmailSpecification): void
{
        Assertion::notEq((string)$this->email, (string)$email, 'New email should be different');

    if (!$uniqueEmailSpecification->isSatisfiedBy($email)) {
        throw new EmailAlreadyExistException('Email already registered.');
    }

    $this->apply(new UserEmailChanged($this->uuid, $email));
}

The same in User::create

jorge07 commented 5 years ago

Hi @ferrius

In SignUp we handle this by UserFactory doing this:

class UserFactory
{
    public function register(UuidInterface $uuid, Credentials $credentials): User
    {
        if ($this->userCollection->existsEmail($credentials->email)) {
            throw new EmailAlreadyExistException('Email already registered.');
        }

        return User::create($uuid, $credentials);
    }

The specification pattern can help also with this removing some code and being more DRY. Good catch 👍

zerai commented 5 years ago

Hi, I think a domain service (interface + implementation) should be better for this type of 'domain rule', not all domain rules must be aggregates invariant. Check uniqueness requirement inside aggregate is not a good idea in this case. Maybe this article can clarify a little bit the difference. Email uniqueness as an aggregate invariant

ferrius commented 5 years ago

Thank you for the article. But see the next article of this author and especially discussion in the comments.

jorge07 commented 5 years ago

I think the point here is that it's needed in more than one place so the UserFactory is taking this responsibility and as soon as this is needed in other places, it gets invalidated. It can also be validated in the application layer (command handler) but I prefer be as close as possible to the domain layer

jorge07 commented 5 years ago

Feedback appreciated here #99 ;)