Projetar validações na camada de modelo de domínioDesign validations in the domain model layer

Em DDD, as regras de validação podem ser consideradas invariáveis.In DDD, validation rules can be thought as invariants. A principal responsabilidade de uma agregação é impor invariáveis entre as alterações de estado para todas as entidades dentro daquela agregação.The main responsibility of an aggregate is to enforce invariants across state changes for all the entities within that aggregate.

Entidades de domínio devem ser sempre entidades válidas.Domain entities should always be valid entities. Existe um determinado número de invariáveis para um objeto que devem ser sempre verdadeiras.There are a certain number of invariants for an object that should always be true. Por exemplo, um objeto de item do pedido sempre deve ter uma quantidade que deve ser um inteiro positivo, além de um nome de artigo e preço.For example, an order item object always has to have a quantity that must be a positive integer, plus an article name and price. Portanto, a imposição de invariáveis é de responsabilidade das entidades de domínio (especialmente da raiz de agregação) e um objeto de entidade não deve ser capaz de existir sem ser válido.Therefore, invariants enforcement is the responsibility of the domain entities (especially of the aggregate root) and an entity object should not be able to exist without being valid. Regras invariáveis simplesmente são expressas como contratos e exceções ou notificações são geradas quando elas são violadas.Invariant rules are simply expressed as contracts, and exceptions or notifications are raised when they are violated.

O raciocínio por trás disso é que vários bugs ocorrerem porque os objetos estão em um estado em que nunca deveriam ter ficado.The reasoning behind this is that many bugs occur because objects are in a state they should never have been in. A seguir está uma boa explicação de Greg Young em uma discussão online:The following is a good explanation from Greg Young in an online discussion:

Vamos propor que agora temos um SendUserCreationEmailService que usa um UserProfile..., como podemos racionalizar nesse serviço que o Nome não é nulo?Let's propose we now have a SendUserCreationEmailService that takes a UserProfile ... how can we rationalize in that service that Name is not null? Podemos verificar novamente?Do we check it again? Ou, mais provável… você apenas não se preocupa em verificar e "espera pelo melhor": você espera que alguém tenha se preocupado em validá-lo antes de enviá-lo a você.Or more likely ... you just don't bother to check and "hope for the best"—you hope that someone bothered to validate it before sending it to you. É claro que, usando TDD, um dos primeiros testes que devemos escrever é que, se eu enviar um cliente com um nome nulo, isso deverá gerar um erro.Of course, using TDD one of the first tests we should be writing is that if I send a customer with a null name that it should raise an error. Porém, quando começarmos a escrever esses tipos de testes repetidamente, percebemos… "espere, se nunca tivéssemos permitido que o nome se tornasse nulo, não teríamos todos esses testes"But once we start writing these kinds of tests over and over again we realize ... "wait if we never allowed name to become null we wouldn't have all of these tests"

Implementar validações na camada de modelo de domínioImplement validations in the domain model layer

As validações normalmente são implementadas em construtores de entidade de domínio ou em métodos que podem atualizar a entidade.Validations are usually implemented in domain entity constructors or in methods that can update the entity. Existem várias maneiras de implementar validações, como verificar dados e aumentar exceções se a validação falhar.There are multiple ways to implement validations, such as verifying data and raising exceptions if the validation fails. Também há padrões mais avançados, como usar o padrão de Especificação para validações e o padrão de Notificação para retornar uma coleção de erros, em vez de retornar uma exceção para cada validação conforme ela ocorre.There are also more advanced patterns such as using the Specification pattern for validations, and the Notification pattern to return a collection of errors instead of returning an exception for each validation as it occurs.

Validar condições e gerar exceçõesValidate conditions and throw exceptions

O exemplo de código a seguir mostra a abordagem mais simples de validação em uma entidade de domínio gerando uma exceção.The following code example shows the simplest approach to validation in a domain entity by raising an exception. Na tabela de referências no final desta seção, você encontrará links para implementações mais avançadas com base nos padrões que discutimos anteriormente.In the references table at the end of this section you can see links to more advanced implementations based on the patterns we have discussed previously.

public void SetAddress(Address address)
{
    _shippingAddress = address?? throw new ArgumentNullException(nameof(address));
}

Um exemplo melhor seria demonstrar a necessidade de garantir que o estado interno não tenha mudado ou que todas as mutações para um método ocorreram.A better example would demonstrate the need to ensure that either the internal state did not change, or that all the mutations for a method occurred. Por exemplo, a implementação a seguir deixará o objeto em um estado inválido:For example, the following implementation would leave the object in an invalid state:

public void SetAddress(string line1, string line2,
    string city, string state, int zip)
{
    _shippingAddress.line1 = line1 ?? throw new ...
    _shippingAddress.line2 = line2;
    _shippingAddress.city = city ?? throw new ...
    _shippingAddress.state = (IsValid(state) ? state : throw new …);
}

Se o valor do estado for inválido, a primeira linha de endereço e a cidade já terão sido alteradas.If the value of the state is invalid, the first address line and the city have already been changed. Isso pode tornar o endereço inválido.That might make the address invalid.

Uma abordagem semelhante pode ser usada no construtor da entidade, gerando uma exceção para garantir que a entidade seja válida quando for criada.A similar approach can be used in the entity’s constructor, raising an exception to make sure that the entity is valid once it is created.

Usar atributos de validação no modelo com base em anotações de dadosUse validation attributes in the model based on data annotations

Anotações de dados, como os atributos Required ou MaxLength necessários, pode ser usado para configurar propriedades de campo de banco de dados do EF Core, conforme explicado em detalhes na seção Mapeamento de tabela, mas elas não funcionam mais para validação de entidade no EF Core (o método IValidatableObject.Validate também não funciona mais para isso) como ocorria desde o EF 4.x no .NET Framework.Data annotations, like the Required or MaxLength attributes, can be used to configure EF Core database field properties, as explained in detail in the Table mapping section, but they no longer work for entity validation in EF Core (neither does the IValidatableObject.Validate method), as they have done since EF 4.x in .NET Framework.

Anotações de dados e a interface IValidatableObject ainda podem ser usados para validação do modelo durante o model binding, antes da invocação de ações do controlador como de costume, mas esse modelo deve ser um ViewModel ou DTO e essa é uma preocupação do MVC ou da API e não uma preocupação do modelo de domínio.Data annotations and the IValidatableObject interface can still be used for model validation during model binding, prior to the controller’s actions invocation as usual, but that model is meant to be a ViewModel or DTO and that’s an MVC or API concern not a domain model concern.

Tendo esclarecido a diferença conceitual, você ainda poderá usar anotações de dados e IValidatableObject na classe de entidade para a validação se as ações receberem um parâmetro de objeto de classe de entidade, o que não é recomendado.Having made the conceptual difference clear, you can still use data annotations and IValidatableObject in the entity class for validation, if your actions receive an entity class object parameter, which is not recommended. Nesse caso, a validação ocorrerá após o model binding, antes de invocar a ação, e você poderá verificar a propriedade ModelState.IsValid do controlador para verificar o resultado. No entanto, isso agora acontece no controlador e não mais antes de persistir o objeto de entidade no DbContext, como ocorria desde o EF 4.x.In that case, validation will occur upon model binding, just before invoking the action and you can check the controller’s ModelState.IsValid property to check the result, but then again, it happens in the controller, not before persisting the entity object in the DbContext, as it had done since EF 4.x.

Você ainda pode implementar a validação personalizada na classe de entidade usando anotações de dados e o método IValidatableObject.Validate, substituindo o método SaveChanges do DbContext.You can still implement custom validation in the entity class using data annotations and the IValidatableObject.Validate method, by overriding the DbContext’s SaveChanges method.

Você pode ver um exemplo de implementação para validar entidades IValidatableObject neste comentário no GitHub.You can see a sample implementation for validating IValidatableObject entities in this comment on GitHub. Esse exemplo não faz validações baseadas em atributo, mas eles devem ser fáceis de implementar usando reflexão na mesma substituição.That sample doesn’t do attribute-based validations, but they should be easy to implement using reflection in the same override.

No entanto, de um ponto de vista de DDD, o modelo de domínio é mantido mais enxuto com o uso de exceções em seus métodos de comportamento da entidade ou implementando os padrões de Especificação e Notificação para impor regras de validação.However, from a DDD point of view, the domain model is best kept lean with the use of exceptions in your entity’s behavior methods, or by implementing the Specification and Notification patterns to enforce validation rules.

Pode fazer sentido usar anotações de dados na camada de aplicativo em classes ViewModel (em vez de entidades de domínio) que aceitem a entrada para permitir a validação do modelo na camada da interface do usuário.It can make sense to use data annotations at the application layer in ViewModel classes (instead of domain entities) that will accept input, to allow for model validation within the UI layer. No entanto, isso não deve ser feito na exclusão de validação dentro do modelo de domínio.However, this should not be done at the exclusion of validation within the domain model.

Validar entidades implementando o padrão de Especificação e o padrão de NotificaçãoValidate entities by implementing the Specification pattern and the Notification pattern

Por fim, uma abordagem mais elaborada para implementar a validação no modelo de domínio é implementando o padrão de Especificação em conjunto com o padrão de Notificação, conforme explicado em alguns dos recursos adicionais listados posteriormente.Finally, a more elaborate approach to implementing validations in the domain model is by implementing the Specification pattern in conjunction with the Notification pattern, as explained in some of the additional resources listed later.

Vale a pena mencionar que você também pode usar apenas um desses padrões — por exemplo, validação manual com instruções de controle, mas usando o padrão de Notificação para empilhar e retornar uma lista de erros de validação.It is worth mentioning that you can also use just one of those patterns—for example, validating manually with control statements, but using the Notification pattern to stack and return a list of validation errors.

Usar a validação adiada no domínioUse deferred validation in the domain

Existem várias abordagens para lidar com validações adiadas no domínio.There are various approaches to deal with deferred validations in the domain. Em seu livro Implementing Domain-Driven Design (Implementando design controlado por domínio), Vaughn Vernon discute isso na seção sobre a validação.In his book Implementing Domain-Driven Design, Vaughn Vernon discusses these in the section on validation.

Validação de duas etapasTwo-step validation

Considere também a validação de duas etapas.Also consider two-step validation. Use a validação em nível de campo em seu comando de DTOs (Objetos de Transferência de Dados) e a validação em nível de domínio dentro de suas entidades.Use field-level validation on your command Data Transfer Objects (DTOs) and domain-level validation inside your entities. Você pode fazer isso retornando um objeto de resultado, em vez de exceções para tornar mais fácil lidar com os erros de validação.You can do this by returning a result object instead of exceptions in order to make it easier to deal with the validation errors.

Usando a validação de campo com anotações de dados, por exemplo, você não duplica a definição de validação.Using field validation with data annotations, for example, you do not duplicate the validation definition. A execução, no entanto, pode estar do lado do servidor e do lado do cliente no caso de DTOs (comandos e ViewModels, por exemplo).The execution, though, can be both server-side and client-side in the case of DTOs (commands and ViewModels, for instance).

Recursos adicionaisAdditional resources