Recentemente, li muito sobre entidades de domínio Always Valid. Eu acredito que, para garantir que as entidades sejam sempre válidas, preciso:
1) Remova a obsessão primitiva e coloque regras de validação / domínio nos construtores de objetos de valor, conforme explicado aqui: https://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/ . 2) Coloque regras de validação / domínio no construtor de entidades ou nos configuradores de propriedades, conforme explicado aqui: http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design-ddd/ .
No entanto, analiso alguns projetos de código aberto como este: https://github.com/gregoryyoung/mr . Pelo que entendi, o autor deste projeto é um defensor do modelo de domínio sempre válido e, no entanto, olho para a classe InventoryItem: https://github.com/gregoryyoung/mr/blob/master/SimpleCQRS/Domain.cs . Percebo que sou capaz de fazer isso:
InventoryItem inventoryItem = new InventoryItem();
ou isto:
InventoryItem inventoryItem2 = new InventoryItem(Guid.Empty,null);
Na minha opinião, isso significa que a entidade foi inicializada em um estado inválido. Esse parece ser o caso em todos os outros projetos de código aberto que eu observei recentemente, por exemplo, este: https://github.com/dcomartin/DDD-CQRS-ES-Example/blob/master/src/Domain /Customer.cs .
Sei que há validação contextual nesse projeto de código aberto ( https://martinfowler.com/bliki/ContextualValidation.html ). Também percebo que os ORMs precisam de um construtor vazio padrão se mapeados para o modelo de domínio.
Um objeto de domínio está em um estado válido se for inicializado com valores padrão usando um construtor de argumento zero / inicializado com valores vazios / nulos?
fonte
Respostas:
Antes de abordar os tipos de considerações que entram na criação de objetos, primeiro vamos abordar a motivação por trás da sua pergunta: a frase "Entidades de domínio sempre válidas". Essa frase é, na melhor das hipóteses, enganosa e faz pouco sentido no contexto do DDD. Isso deve ser aparente por dois motivos relacionados:
A primeira é que, implicitamente, ele muda seu foco do comportamento do sistema, pedindo que você considere a validação apenas em termos de estado. Embora superficialmente isso pareça fazer sentido (é claro que a validação é em termos de estado!), Você deve lembrar que o princípio fundamental do DDD é que um sistema seja modelado de acordo com o comportamento . A motivação para isso é simplesmente que o contexto, ou o próprio processo de negócios , costuma ser uma consideração importante ao determinar se algum estado é válido ou não. Modelar um sistema dessa maneira pode reduzir bastante sua complexidade.
Isso nos leva à segunda razão, que diz respeito aos requisitos práticos que esse sistema implicaria. Para criar um sistema de "Entidades de domínio sempre válidas", seria necessário modelar cada permutação de estado de acordo com os processos de negócios nos quais o estado é usado. Um exemplo simples pode ilustrar as limitações disso:
Regras:
Customer
deve ter mais de 18 anos para se registrarCustomer
deve ter menos de 25 anos para se qualificar para o desconto no registroCustomer
deve ter mais de 25 anos para fazer a reservaA primeira coisa que você deve observar é que todas essas regras (como quase todas as regras) se aplicam a algum processo de negócios. Eles não existem no vácuo. Essas regras seriam validadas em
customer.Register()
ecustomer.Reserve()
. Isso resulta em um paradigma muito mais descritivo e declarativo, porque fica claro onde as regras estão sendo executadas.Se quiséssemos para modelar essas regras tal que resulta em sistema de "entidades de domínio sempre válidas" seria preciso particionar o nosso
Customer
emRegistrar
eReserver
entidades. E embora isso possa não parecer tão ruim para este exemplo, à medida que as regras se tornam mais complexas e abundantes, você terminará com uma classe de explosão como essa que representa o estado "dentro" de algum contexto ou processo. Isso é simplesmente desnecessário e inevitavelmente criará problemas quando dois desses objetos dependem da mesma fatia de estado.Além disso, algo como
Customer c = new Customer()
é um mau lugar para lançar uma exceção, porque não está claro quais regras de negócios podem ser aplicadas. O que nos leva à nossa discussão final.Vou apenas dizer que deve haver zero validação de regras de negócios acontecendo em construtores. A construção de objetos não tem nada a ver com o domínio de negócios e, por esse motivo, além dos motivos acima relacionados a contexto e coerência, todas as regras de negócios devem ser aplicadas nos corpos de métodos de uma entidade (é provável que os métodos sejam nomeados após os processos de negócios corretamente) ?).
Além disso, "atualizar" um objeto não é a mesma coisa que criar uma nova entidade em seu domínio. Objetos não surgem do nada. Se houver regras comerciais sobre como uma nova entidade pode entrar no seu sistema, ela deverá ser modelada no seu domínio . Aqui estão algumas discussões adicionais sobre o assunto por um verdadeiro mestre http://udidahan.com/2009/06/29/dont-create-aggregate-roots/
fonte
Pode ser.
Não há nada contra as regras sobre a criação de um objeto de domínio válido usando um construtor padrão.
Não há nada contra as regras sobre a criação de um objeto de domínio válido com valores vazios.
Não há nada contra as regras sobre a criação de um objeto de domínio válido que não possui elementos opcionais.
Não há nada contra as regras sobre a criação de um objeto de domínio válido usando valores nulos.
Onde os problemas ocorrem: a criação de objetos de domínio que não obedecem à sua própria álgebra semântica.
A implementação correta depende de uma interpretação correta da semântica.
É normal ter uma representação perdoadora de um conceito de domínio e, por etapas, aplicar restrições adicionais. Essa é uma das áreas em que as linguagens de implementação que facilitam a adição de tipos (ex: F #) têm vantagens sobre linguagens desajeitadas, como Java.
Por exemplo, se tivermos um domínio que se preocupa com números e, dentro desse domínio, existem alguns recursos específicos para números primos, ou primos de Mersenne, um mecanismo natural a ser usado é criar três tipos diferentes; tornando explícita a noção de que existem diferentes conjuntos de restrições a serem aplicados às entradas em diferentes partes da sua solução.
Nesse tipo de design, a "validação" de que o número é realmente um Prime existiria no próprio construtor Prime. Em um contexto em que precisamos da restrição adicional de que o parâmetro é primo de Mersenne , usamos uma conversão
Prime
->MersennePrime
, garantindo que o conhecimento da restrição de Mersenne tenha uma autoridade ("não se repita")."Tornar implícito, explícito"
fonte