Zero construtores de argumento e entidades Always Valid

8

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?

w0051977
fonte
2
Você realmente não pode generalizar. Pode ser. Pode não ser. Isso importa? IMHO não, especialmente as conversas iniciais sobre DDD esqueceram de pensar que a realidade é diferente e, por exemplo, ORMs / idiomas / estruturas ditam algumas regras (e o preço para contorná-las pode ser muito alto ou não vale a pena). Ok, também projetam que você listou não parecem ser melhor código possível .NET sempre ...
Adriano Repetti
@ Adriano Repetti, você conhece algum bom projeto de código aberto cqrs? Você coloca a validação em seus comandos?
W0051977
2
Eu distinguiria a validação de parâmetros da validação de domínio (quando aplicável). Por exemplo, um parâmetro nulo não tem nada a ver com o domínio, ele vem de um detalhe de implementação (idioma que você está usando). O primeiro que eu sempre faço, não importa o que e onde. A validação de domínio é mais complicada, mas se você realmente se preocupa com invariantes quebrados, IMO, considere objetos imutáveis . Eles são (novamente apenas na minha opinião) uma, muitas vezes, melhor troca de uma guerra santa contra línguas que realmente usar (e isso era uma crítica para muitos DDD mais velho leva)
Adriano Repetti

Respostas:

7

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 registrar
  • Customer deve ter menos de 25 anos para se qualificar para o desconto no registro
  • Customer deve ter mais de 25 anos para fazer a reserva

A 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()e customer.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 Customerem Registrare Reserverentidades. 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/

deslize do lado do rei
fonte
3

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?

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.

Number n = new Number(...)
Optional<Prime> p = n.prime()
if (p.isPresent()) {
    Optional<MersennePrime> mp = p.mersennePrime()
    // ...
} 

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"

VoiceOfUnreason
fonte
Obrigado. Você poderia explicar o que é álgebra semântica, talvez com um exemplo? Você também pode explicar como o exemplo de código é relevante. +1 para "não há nada contra ....".
w0051977
Certamente uma entidade sem um ID vazio é uma coisa ruim? A menos que haja cenários em que você inicialize a classe de domínio com valores padrão e a crie de forma incremental?
w0051977 12/06