Eu ainda estou procurando as melhores práticas para validação de modelo de domínio. É bom colocar a validação no construtor do modelo de domínio? meu exemplo de validação de modelo de domínio da seguinte maneira:
public class Order
{
private readonly List<OrderLine> _lineItems;
public virtual Customer Customer { get; private set; }
public virtual DateTime OrderDate { get; private set; }
public virtual decimal OrderTotal { get; private set; }
public Order (Customer customer)
{
if (customer == null)
throw new ArgumentException("Customer name must be defined");
Customer = customer;
OrderDate = DateTime.Now;
_lineItems = new List<LineItem>();
}
public void AddOderLine //....
public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}
public class OrderLine
{
public virtual Order Order { get; set; }
public virtual Product Product { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal UnitPrice { get; set; }
public OrderLine(Order order, int quantity, Product product)
{
if (order == null)
throw new ArgumentException("Order name must be defined");
if (quantity <= 0)
throw new ArgumentException("Quantity must be greater than zero");
if (product == null)
throw new ArgumentException("Product name must be defined");
Order = order;
Quantity = quantity;
Product = product;
}
}
Obrigado por todas as suas sugestões.
fonte
Apesar de esta questão ser um pouco obsoleta, gostaria de acrescentar algo que vale a pena:
Gostaria de concordar com @MichaelBorgwardt e estender trazendo à tona a testabilidade. Em "Trabalhando efetivamente com o código herdado", Michael Feathers fala muito sobre obstáculos aos testes e um desses obstáculos é "difícil de construir" objetos. A construção de um objeto inválido deve ser possível e, como sugere Fowler, as verificações de validade dependentes do contexto devem ser capazes de identificar essas condições. Se você não conseguir descobrir como construir um objeto em um equipamento de teste, terá problemas ao testar sua classe.
Quanto à validade, gosto de pensar em sistemas de controle. Os sistemas de controle funcionam analisando constantemente o estado de uma saída e aplicando ações corretivas à medida que a saída se desvia do ponto de ajuste, isso é chamado de controle de malha fechada. O controle de loop fechado intrinsecamente espera desvios e age para corrigi-los e é assim que o mundo real funciona, e é por isso que todos os sistemas de controle reais geralmente usam controladores de loop fechado.
Eu acho que o uso de validação dependente de contexto e objetos fáceis de construir facilitará o trabalho do sistema no futuro.
fonte
Como eu tenho certeza que você já sabe ...
A verificação da validade dos dados passados como parâmetros c'tor é definitivamente válida no construtor - caso contrário, você está possivelmente permitindo a construção de um objeto inválido.
No entanto (e esta é apenas a minha opinião, não é possível encontrar bons documentos neste momento) - se a validação de dados exigir operações complexas (como operações assíncronas - talvez validação com base no servidor se estiver desenvolvendo um aplicativo de desktop), é melhor coloque uma função de inicialização ou validação explícita de algum tipo e os membros configurem os valores padrão (como
null
) no c'tor.Além disso, apenas como uma nota lateral, como você a incluiu no seu exemplo de código ...
A menos que você esteja realizando uma validação adicional (ou outra funcionalidade)
AddOrderLine
, provavelmente exporia aList<LineItem>
propriedade como propriedade, em vez deOrder
agir como fachada .fonte
AddLineItem
método. De fato, para DDD, isso é preferido. SeList<LineItem>
for alterado para um objeto de coleção personalizado, a propriedade exposta e tudo o que dependia de umaList<LineItem>
propriedade estão sujeitos a alterações, erros e exceções.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".
fonte