📜 ⬆️ ⬇️

Validation: inside entities or outside?

Please note that although the post is written in the first person, this is a translation of an article from the blog by Jimmy Bogard, author of AutoMapper.

People often ask me, especially in the context of vertical slice architecture, where should the validation take place? If you use DDD, you can put validation inside entities. But personally, I think that validation doesn't really fit into the responsibility of the entity.

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

There are two problems with this approach:

And although you can show validation errors (usually generated by ORM) 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 to DDD, you can work around a changing state problem 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 take some version of the result 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; } } 

Again, displaying an error to the user is annoying, since only one error is returned at a time. I could return them to everyone, but how then can I match them with the names of the fields on the screen? No Obviously, entities are sucks for team validation. However, validation frameworks are great for this.
')

Validation of command (command validation)


Instead of shifting the validation command to the entity / aggregate, I completely rely on invariants. The whole essence of invariants is in the assurance that I can move from one state to another entirely and not partially. That is, in fact, this is not about the validation of the request, but about the implementation of the transition between states.

With this approach, my validation is built around commands 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 command is valid, can I apply it to my entities to transfer them to a new state. Inside the entity, I just have to process the ChangeNameCommand command and make the transition to the new state, being sure that my invariants are being executed. In many projects I use FluentValidation:
 public class ChangeNameCommand { public string FirstName { get; set; } public string LastName { get; set; } } public class ChangeNameValidator : AbstractValidator<ChangeNameCommand> { 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 and of 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 perfectly correlate with the interface, since it was around the team that this interface was built in the first place.

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

Source: https://habr.com/ru/post/282878/


All Articles