DDD, PHP - where to perform the validation?
Asked Answered
T

2

6

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.

Teuton answered 4/8, 2016 at 15:0 Comment(1)
This post might help you a bitChristcross
B
6

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.

<?php
$userRepository
    ->containsUserForEmail($emailValueObject)
    ->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.

Bolden answered 4/8, 2016 at 15:58 Comment(3)
Thank you for your answer. In terms of validating data that need access to the repository. Let's consider the example. We have an aggregate "Invoice". There's a method Invoice::addItem(InvoiceItem $item). Let's introduce a business rule - max 5 items allowed in a single invoice (handled by aggregate). Also, introduce a command AddItemsToInvoice(InvoiceId $invoiceId, array $invoiceItems). So - to validate the business rule, Command Handler has to load Invoice aggregate, assign it with all the existing items, add then add new item. Is it the right approach? We use repositories in commands.Teuton
When you say "We use repositories in commands" I hope you mean Command Handlers. Repositories should not be injected into Commands. Commands are a special kind of DTOs (or messages) that carry the semantics of the User's intent and should only contain data that is needed to complete the request. And yes, in the Handler you load the Aggregate from the Repository, call the Invoice::addItems($invoiceItems) method (maybe receive an Exception due to the business rule) and lastly save the Aggregate within the single transaction boundary that the Aggregate represents.Bolden
Yes, it should be "We use repositories in Command Handlers". Can't edit my comment now. This is exactly what I wanted to know. Thank you!Teuton
A
3

Just to expand on what tPl0ch said, as I have found helpful... While I have not been in the PHP stack in many years, this largely is theoretical discussion, anyhow.

One of the larger problems faced in practical applications of DDD is that of validation. Traditional logic would dictate that validation has to live somewhere, where it really should live everywhere. What has probably tripped people up more than anything, when applying this to DDD is the qualities of a domain never being "in an invalid state". CQRS has gone a far way to address this, and you are using commands.

Personally, the way that I do this, is that commands are the only way to alter state. Even if I require the creation of a domain service for a complex operation, it is the commands which will do the work. A traditional command handler will dispatch a command against an aggregate and put the aggregate into a transitional state. All of this is fairly standard, but I additionally delegate the responsibility of validation of the transition to the commands themselves, as they already encompass business logic, as well. If I am creating a new Account, for example, and I require a first name, last name, and email address, I should be validating that as being present in the command, before it ever is attempted to be applied to the aggregate through the command handler. As such, each of my command handlers have not just awareness of the command, but also a command validator.

This validator ensures that the state of the command will not compromise the domain, which allows me to validate the command itself, and at a point where I do not incur additional cost related to having to validate somewhere in the infrastructure or implementation. Since the only way that I have to mutate state is solely in the commands, I do not put any of that logic directly into the domain objects themselves. That is not to say that the domain model is anemic, far from it, actually. There is an assumption that if you are not validating in the domain objects themselves, that the domain immediately becomes anemic. But, the aggregate needs to expose the means to set these values - generally through a method - and the command is translated to provide these values to that method. On of the semi-common approaches that you see is that logic is put into the property setters, but since you are only setting a single property at a time, you could more easily leave the aggregate in an invalid state. If you look at the command as being validated for the purpose of mutating that state as a single operation, you see that the command is a logical extension of the aggregate (and from a code organizational standpoint, lives very near, if not under, the aggregate).

Since I am only dealing with command validation at that point, I generally will have persistence validation, as well. Essentially, right before the aggregate is persisted, the entire state of the aggregate will be validated at once. The ultimate goal is to get a command to persist, so that means that I will have a single persistence validator per aggregate, but as many command validators as I have commands. That single persistence validator will provide the infallible validation that the command has not mutated the aggregate in a way that violates the overarching domain concerns. It will also have awareness that a single aggregate can have multiple valid transitional states, which is something not easily caught in a command. By multiple states, I mean that the aggregate may be valid for persistence as an "insert" for persistence, but perhaps is not valid for an "update" operation. The easiest example of that would be that I could not update or delete an aggregate which has not been persisted.

All of these can be surfaced to the UI, in my own implementation. The UI will hand the data to an application service, the application service will create the command, and it will invoke a "Validate" method on my handler which will return any validation failures within the command without executing it. If validation errors are present, the application service can yield to the controller, returning any validation errors that it finds, and allow them to surface up. Additionally, pre-submit, the data can be sent in, follow the same path for validation, and return those validation errors without physically submitting the data. It is the best of both worlds. Command violations can happen often, if the user is providing invalid input. Persistence violations, on the other hand, should happen rarely, if ever at all, outside of testing. It would imply that a command is mutating state in a way that is not supported by the domain.

Finally, post-validation of a command, the application service can execute it. The way that I have built my own infrastructure is that the command handler is aware of if the command was validated immediately before execution. If it was not, the command handler will execute the same validation that is exposed by the "Validate" method. The difference, however, is that it will be surfaced as an exception. Goal at this point is to halt execution, as an invalid command cannot enter the domain.

Although the samples are in Java (again, not my platform of choice), I highly recommend Vaughn Vernon's "Implementing Domain-Driven Design". It really pulls a lot of the concepts in the Evans' material together with the advances in the DDD paradigm, such as CQRS+ES. At least for me, the material in Vernon's book, which is also a part of the "DDD Series" of books, changed the way I fundamentally approach DDD as much as the Blue Book introduced me to it.

Alfred answered 5/8, 2016 at 21:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.