Validation: inside entities or outside?

Original author: Jimmy Bogard
  • Transfer
Please note that although the post is written in the first person, it is a translation of an article from Jimmy Bogard's blog by AutoMapper.

I am often asked, especially in the context of vertical slice architecture, where should validation take place? If you use DDD, you can put validation inside entities. But personally, I believe that validation does not fit very well with the responsibility of the entity.

Often validation within entities is done using annotations. Suppose we have a Customer and its FirstName / LastName fields are required:
public class Customer
{
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }
}

There are two problems with this approach:
  • You change the state of the entity before validation, that is, your entity may be in an invalid state
  • The context of the operation is unclear (what exactly is the user trying to do)

And although you can show validation errors (usually generated by ORMs) to the user, it’s not so easy to compare the original intentions and the details of the state implementation. As a rule, I try to avoid this approach.

However, if you stick with DDD, you can work around the problem of changing state by adding a method:
public class Customer
{
  public string FirstName { get; private set; }
  public string LastName { get; private set; }
  public void ChangeName(string firstName, string lastName) {
    if (firstName == null)
      throw new ArgumentNullException(nameof(firstName));
    if (lastName == null)
      throw new ArgumentNullException(nameof(lastName));
    FirstName = firstName;
    LastName = lastName;
  }
}

A little better, but only a little, because exceptions are the only way to show validation errors. You don’t like exceptions, so you take some variant of the result of the [execution] of the command (command result):
public class Customer
{
  public string FirstName { get; private set; }
  public string LastName { get; private set; }
  public CommandResult ChangeName(ChangeNameCommand command) {
    if (command.FirstName == null)
      return CommandResult.Fail("First name cannot be empty.");
    if (lastName == null)
      return CommandResult.Fail("Last name cannot be empty.");
    FirstName = command.FirstName;
    LastName = command.LastName;
    return CommandResult.Success;
  }
}

And again, displaying the error to the user is annoying, since only one error is returned at a time. I could return them all together, but how then can I compare them with the names of the fields on the screen? No way. Obviously, entities are badly suited for team validation. However, validation frameworks are great for this.

Command validation


Instead of shifting command validation to an entity / aggregate, I rely entirely on invariants. The whole point of invariants is the belief that I can move from one state to another in whole and in full, and not in part. That is, in fact, this is not about query validation, but about the transition between states.

With this approach, my validation is built around teams and actions, not entities. I could do something like this:
public class ChangeNameCommand {
  [Required]
  public string FirstName { get; set; }
  [Required]
  public string LastName { get; set; }
}
public class Customer
{
  public string FirstName { get; private set; }
  public string LastName { get; private set; }
  public void ChangeName(ChangeNameCommand command) {
    FirstName = command.FirstName;
    LastName = command.LastName;
  }
}

My validation attributes are in the team itself, and only if the team is valid, can I apply it to my entities to translate them into a new state. Inside the entity, I just have to process the ChangeNameCommand command and make the transition to a new state, being sure that my invariants are executed. In many projects, I use FluentValidation:
public class ChangeNameCommand {
  public string FirstName { get; set; }
  public string LastName { get; set; }
}
public class ChangeNameValidator : AbstractValidator {
  public ChangeNameValidator() {
    RuleFor(m => m.FirstName).NotNull().Length(3, 50);
    RuleFor(m => m.LastName).NotNull().Length(3, 50);
  }
}
public class Customer
{
  public string FirstName { get; private set; }
  public string LastName { get; private set; }
  public void ChangeName(ChangeNameCommand command) {
    FirstName = command.FirstName;
    LastName = command.LastName;
  }
}

The key difference here is that I validate the team, not the entity. Entities in themselves are not libraries for validation, so it’s much more correct (much cleaner) to do validation at the command level. At the same time, validation errors correlate perfectly with the interface, since it was around the team that this interface was built in the first place.

Validate commands, not entities, and perform the validation at the edges.

Also popular now: