A validação deve ser realizada o mais rápido possível.
A validação em qualquer contexto, seja no modelo de domínio ou em qualquer outra forma de escrever software, deve servir ao propósito do QUE você deseja validar e em que nível você está no momento.
Com base na sua pergunta, acho que a resposta seria dividir a validação.
A validação da propriedade verifica se o valor dessa propriedade está correto, por exemplo, quando um intervalo entre 1 e 10 é excedido.
A validação de objeto garante que todas as propriedades no objeto sejam válidas em conjunto. por exemplo, BeginDate é anterior a EndDate. Suponha que você leia um valor do armazenamento de dados e BeginDate e EndDate sejam inicializados em DateTime.Min por padrão. Ao definir o BeginDate, não há motivo para aplicar a regra "deve ser anterior ao fim do dia", pois isso não se aplica a YET. Esta regra deve ser verificada APÓS todas as propriedades terem sido definidas. Isso pode ser chamado no nível raiz agregado
A validação também deve ser realizada na entidade agregada (ou raiz agregada). Um objeto Order pode conter dados válidos, assim como OrderLines. Porém, uma regra comercial afirma que nenhum pedido pode exceder US $ 1.000. Como você aplicaria essa regra em alguns casos, isso é permitido. você não pode simplesmente adicionar uma propriedade "não validar quantia", pois isso levaria a abusos (mais cedo ou mais tarde, talvez até você, apenas para tirar esse "pedido desagradável" do caminho).
Em seguida, há validação na camada de apresentação. Você realmente enviará o objeto pela rede, sabendo que ele falhará? Ou você poupará o usuário desse ônus e o informará assim que ele inserir um valor inválido. por exemplo, na maioria das vezes o seu ambiente DEV será mais lento que a produção. Gostaria de esperar 30 segundos antes de ser informado de "você esqueceu esse campo novamente durante mais uma execução de teste", especialmente quando há um bug de produção a ser corrigido com seu chefe respirando pelo pescoço?
A validação no nível de persistência deve estar o mais próximo possível da validação do valor da propriedade. Isso ajudará a evitar exceções com a leitura de erros "nulo" ou "valor inválido" ao usar mapeadores de qualquer tipo ou simples leitores de dados antigos. O uso de procedimentos armazenados resolve esse problema, mas requer escrever novamente a mesma lógica de valiação e executá-la novamente. E os procedimentos armazenados são o domínio do administrador do banco de dados, portanto, não tente fazer o trabalho do HIS também (ou pior, incomode-o com essa "escolha minuciosa pela qual ele não está sendo pago").
então, com algumas palavras famosas "depende", mas pelo menos agora você sabe POR QUE depende.
Eu gostaria de poder colocar tudo isso em um único lugar, mas, infelizmente, isso não pode ser feito. Fazer isso colocaria uma dependência em um "objeto Deus" contendo TODA a validação para TODAS as camadas. Você não quer seguir esse caminho sombrio.
Por esse motivo, apenas lancei exceções de validação em um nível de propriedade. Todos os outros níveis que utilizo ValidationResult com um método IsValid para reunir todas as "regras quebradas" e passá-las ao usuário em uma única AggregateException.
Ao propagar a pilha de chamadas, reuno-as novamente em AggregateExceptions até atingir a camada de apresentação. A camada de serviço pode lançar essa exceção diretamente para o cliente no caso do WCF como uma FaultException.
Isso permite que eu pegue a exceção e divida-a para mostrar erros individuais em cada controle de entrada ou achatá-la e mostrá-la em uma única lista. A escolha é sua.
é por isso que também mencionei a validação da apresentação, para curto-circuitos, tanto quanto possível.
Caso você esteja se perguntando por que também tenho a validação no nível de agregação (ou no nível de serviço, se quiser), é porque não tenho uma bola de cristal me dizendo quem usará meus serviços no futuro. Você terá problemas suficientes para encontrar seus próprios erros para impedir que outros cometam seus erros :) digitando dados inválidos. Por exemplo, você administra o aplicativo A, mas o aplicativo B alimenta alguns dados usando seu serviço. Adivinha quem eles perguntam primeiro quando há um bug? O administrador do aplicativo B terá prazer em informar o usuário "não há erro no meu final, apenas insiro os dados".