hopsey hopsey - 1 year ago 110
PHP Question

DDD, PHP - where to perform the validation?

I started playing with DDD recently. Today I'm having a problem with placing validation logic in my application. I'm not sure what layer should I pick up. I searched over the internet and can't find an unified solution that solves my problem.

Let's consider the following example. User entity is represented by ValueObjects such as id (UUID), age and e-mail address.

final class User
* @var \UserId
private $userId;

* @var \DateTimeImmutable
private $dateOfBirth;

* @var \EmailAddress
private $emailAddress;

* User constructor.
* @param UserId $userId
* @param DateTimeImmutable $dateOfBirth
* @param EmailAddress $emailAddress
public function __construct(UserId $userId, DateTimeImmutable $dateOfBirth, EmailAddress $emailAddress)
$this->userId = $userId;
$this->dateOfBirth = $dateOfBirth;
$this->emailAddress = $emailAddress;

Non business logic related validation is performed by ValueObjects. And it's fine.
I'm having a trouble placing business logic rules validation.

What if, let's say, we would need to let Users have their own e-mail address only if they are 18+?
We would have to check the age for today, and throw an Exception if it's not ok.

Where should I put it?

  • Entity - check it while creating User entity, in the constructor?

  • Command - check it while performing Insert/Update/whatever command? I'm using tactician in my project, so should it be a job for

    • Command

    • Command Handler

Where to place validators responsible for checking data with the repository?

Like email uniqueness. I read about the Specification pattern. Is it ok, if I use it directly in Command Handler?

And last, but not least.

How to integrate it with UI validation?

All of the stuff I described above, is about validation at domain-level. But let's consider performing commands from REST server handler. My REST API client expects me to return a full information about what went wrong in case of input data errors. I would like to return a list of fields with error description.
I can actually wrap all the command preparation in try block an listen to Validation-type exceptions, but the main problem is that it would give me information about a single error, until the first exception.
Does it mean, that I have to duplicate my validation logic in controller-level (ie with zend-inputfilter - I'm using ZF2/3)? It sounds incosistent...

Thank you in advance.

Answer Source

I will try to answer your questions one by one and additionally give my two cents here and there and how I would solve the problems.

Non business logic related validation is performed by ValueObjects

Actually ValueObjects represent concepts from your business domain, so these validations are actually business logic validations too.

Entity - check it while creating User entity, in the constructor?

Yes, in my opinion you should try to add this kind of behavior as deep down in the Aggregates as you can. If you put it into Commands or Command Handlers you loose cohesiveness and business logic is leaking out into the Application layer. And I would even go further. Ask yourself the question if there are hidden concepts within your model that are not made explicit. In your case that is an AdultUser and an UnderagedUser (they could both implement a UserInterface) that actually have different behavior. In these cases I always strive for modelling this explicitly.

Like email uniqueness. I read about the Specification pattern. Is it ok, if I use it directly in Command Handler?

The Specification pattern is nice if you want to be able to combine complex queries with logical operators (especially for the Read Model). In your case I think this is an overkill. Adding a simple containsUserForEmail($emailValueObject) method into the UserRepositoryInterface and call this from the Use Case is fine.

    ->hasOrThrow(new EmailIsAlreadyRegistered($emailValueObject));

How to integrate it with UI validation?

So first of all there already should be client side validation for the fields in question. Make it easy to use your system in the right way and hard to use it in the wrong way.

Of course there still needs to be server side validation. We currently use the schema validation approach where we have a central schema registry from which we fetch a schema for a given payload and then can validate JSON payloads against that JSON Schema. If it fails we return a serialized ValidationErrors object. We also tell the client via the Content-Type: application/json; profile=https://some.schema.url/v1/user# header how it can build a valid payload.

You can find some nice articles on how to build a RESTful API on top of a CQRS architecture here and here.