0

I have a question about DDD. I wonder if I should add validation in a web api controller and duplicate it in a command handler? Is it good approach to validate a DTO and a command even if the validation is the same?

For example, here I used data annotation validation attributes for a controller:

public class EmailsController : ControllerBase
{

    [HttpPost]
    public async Task<IActionResult> SendEmail(CreateEmailMessageDTO emailMessage)
    {
        await _mediator.Send(new SendEmailCommand(emailMessage.Email, emailMessage.Subject, emailMessage.Message));
        return NoContent();
    }
}

// Data Annotation Validation:
public class CreateEmailMessageDTO
{   
    [Required]
    [EmailAddress]
    public string Email { get; set; }

    [Required]
    public string Subject { get; set; }

    [Required]
    public string Message { get; set; }
}

And FluentValidation for a command:

public class SendEmailCommand : IRequest
{
    public string Email { get; }
    public string Subject { get; }
    public string Message { get; }

    public SendEmailCommand(string email, string subject, string message)
    {
        EmailFrom = emailFrom;
        Subject = subject;
        Message = message;
    }
}


// FluentValidation:
public class SendEmailCommandValidator : AbstractValidator<SendEmailCommand>
{
    public SendEmailCommandValidator()
    {
        RuleFor(x => x.EmailFrom)
            .NotEmpty()
            .EmailAddress();

        RuleFor(x => x.Subject)
            .NotEmpty();

        RuleFor(x => x.Message)
            .NotEmpty();
    }
}
5
  • I think you are mixing tools. The annotations are for when you do not have an alternative for validation. If you can use FluentValidation, why have duplicate rules? Feb 14, 2022 at 19:48
  • @GregBurghardt those annotations are useful when generating documentation like open api/swagger, in addition to validating API input. Additional command validation with FluentValidation can still be useful if the commands have additional parameters coming from other sources. I’m missing the DDD aspect in this question; is there even any business domain logic when sending emails?
    – Rik D
    Feb 14, 2022 at 23:20
  • I don't have any business logic for sending email. Feb 15, 2022 at 5:00
  • DDD is about designing software that solves business problems. Since this question is not about that, please update the question and remove the DDD references.
    – Rik D
    Feb 15, 2022 at 6:31
  • done............................... Feb 15, 2022 at 10:25

3 Answers 3

3

First, you need to step back and take a lager picture of what you are doing.

The purpose of the domain layer is to model your business concepts using OOP, and implement business requirements checks to ensure your service integrity, business-wise. All operations in that layer are application independent and should execute in any piece of software related to that domain.

The presentation layer, on the other hand, is responsible for mediation between customer facing interface of a specific application, and this application's other layers. Frameworks such as asp.net webapi hide some cross cutting concerns such as authentication, and does serialization/deserialization for you, so you are left with less code to write for this layer.

Put another way, the presentation layer is responsible for enforcing your interface specification, and your domain layer is responsible for enforcing business requirements. The relevant object models for these two might differ, which is why you may have conversion operations, and presentation level validation may not suffice to enforce business invariants.

Now, where to validate ? The answer is quite straightforward. If your validation is a business requirement (I cannot send an email from a malformed email address, whatever the application), then this validation should happen in the domain layer. If the email formatting is a requirement in your interface specification (EmailsController expects CreateEmailMessageDTO.Email conforming to RFC), then validation should happen in the presentation layer.

Business requirements should never depend solely on presentation layer validation, otherwise business integrity depends on interface specification, violating the single responsibility principle (SRP) and DDD principles (domain validation happens in the domain layer only). But what if the validation is both a business requirement and an interface specification ?

You basically have two options :

  • implement in business layer only
  • implement in both layers

Implementing the validation in business layer only is obviously cheaper and easier, don't-repeat-yourself (DRY) approach, as you need to write less code, so your application is smaller, easier to understand and maintain. Your presentation layer does not need to validate because the domain layer will do that for you, just think of that as a delegation. The drawback is that, sometimes, business layer validation can happen quite late in the request life-cycle. You will eventually need to query business data from the persistence layer before being able to validate your business state. In this situation, it's up to you to decide whether you prefer to fail early by adding a presentation layer validation or keep the code smaller.

In your situation though, the additional cost of adding email format validation in the presentation layer is very small, both at the code maintenance level and the runtime execution footprint level. Doing both validation seems acceptable, unless you are in a specific context, such as very high throughput low available memory, which might advocate for smallest footprint over maintainability. However in such situation you might reconsider your overall architecture, but that's another topic.

1
2

I wonder if I should add validation in a web api controller

Probably - although you might want parsing, rather than validation. Making your checks in the controller both simplifies your error handling (the controller can readily reply to messages that don't parse with a client error using the appropriate status code) and also simplifies the rest of your code (which can work with a trusted in memory representation of information).

Important note: CreateEmailMessageDTO is an in memory representation of a message in your API. You might have, for example, an API document that describes a CreateEmail message, which fields are required, which are optional, other constraints on the information. The parsing you are doing here is to ensure that the general purpose information structure you have been passed satisfies the stricter constraints of your API schema.


Is it good approach to validate a DTO and a command even if the validation is the same?

Often, not always.

The trick is that our API constraints and our data model constraints change for different reasons; it can be useful to make that distinction explicit in the code (there's some tension between "expressive" and "YAGNI").

During the period where they are the same, you might want to make them the same (these two cases both delegate to the same thing... or each delegate to local routines that are generated from the same constraint definitions). There are a lot of possibilities here, depending on where you want the dependency arrows to go.

For instance, CreateEmailMessageDTO.Email and SendEmailCommand.Email likely use "the same" validation because they are both coupled to the production rules defined by RFC 5322.

You might solve this by implementing the validation twice, or having both of these delegate to a common implementation, or by generating "different" validators from the same production rules. Welcome to trade offs.

Continuing to support validation on the command object can still be valuable if there are other code paths that produce command objects without going through the web api models.

1
-1

You should have 1 DDD tier (layer) that performs validation check on values being in the valid range, not 2 tiers. Ideally upon validation, you would change the type from UnvalidatedX to AlreadyValidatedX (or shorter identifiers/acronyms of your team's choosing) in a strongly-typed language that would enforce this strong typing at engineering-time (e.g., compile-time), then utilize only AlreadyValidatedX henceforth for all interior tiers of processing. More-interior tiers would utilize the AlreadyValidatedX type and never utilize the UnvalidatedX type whatsoever. In this viewpoint, the answer to OP's question becomes trivially obvious. Such strong typing is present in TypeScript; the lack of such strong typing is present in JavaScript—hence why TypeScript was invented to provide strong typing to JavaScript to catch such bugs as usage of unvalidated input at engineering-time. In this viewpoint, the validation would occur just prior to instantiating a DTO that carries instances of AlreadyValidatedX further interior to the system.

But we live in an imperfect world where some interior tier might be implemented in a programming language that lacks strong typing (or that goes through a glue layer where the strong typing of one programming language is not relayed to another programming). Thus, your team would need to enforce such rules via coding standards, team engineering practice, and code reviews—hence why strongly-typed languages are preferable to automate this otherwise costly manual labor. The closer that human beings who are (for whatever reason) using a weakly-typed set of programming languages can emulate via labor the automation that would have been provided by using strongly-typed programming languages whose types interwork between tiers, the better.

4
  • 2
    The OP uses C#, yet most of your answer is about strong vs weak typing. Why?
    – Rik D
    Feb 14, 2022 at 23:12
  • @RikD, because the answer from a •software engineering• (!) perspective is the same, independent of programming language: use a new type. Programming-language-specific C# questions should be directed to StackOverflow instead, as the focus there would be on C# itself instead of •software engineering• as an engineering-best-practice discipline. No portion of my strong-typing answer is prohibited in C#. Just because OP mistakenly wrote a leading question that misleads in the wrong direction of seeking a C#-specific answer (in SE instead of SO) doesn't mean I must follow the mislead. Feb 15, 2022 at 2:04
  • 1
    Context matters. When we already know a question is about how to solve problem X in a strongly typed language, it serves no purpose to bring up weakly types languages in an answer imo. Your suggestion to create a specific type for the validated model is interesting, I just don’t think the debate about weak vs strong is necessary in this case.
    – Rik D
    Feb 15, 2022 at 12:07
  • Yes, context does very much matter! Context of Software Engineering on StackExchange gets the answer that is correct as a best practice as a well-studied engineering discipline under W. Edwards Deming's school of define it, measure it, improve it. Context of StackOverflow would get the C# idiomatic tweak-your-C#-source-code-using-this-one-weird-trick answer (which is relatively worthless as a pedagogical technique of learning the higher wisdom of that leads to automating proof of correctness). On Software Engineering I teach the higher wisdom leading toward automating proof of correctness. Feb 15, 2022 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.