Valores computados e leituras simples - uma dor incômoda para meus projetos orientados a domínios!

9

O problema que enfrento continuamente é como lidar com valores computados conduzidos pela lógica do domínio enquanto ainda trabalhamos eficientemente no armazenamento de dados.

Exemplo:

Estou retornando uma lista de produtos do meu repositório por meio de um serviço. Essa lista é limitada pelas informações de paginação do DTO da solicitação enviado pelo cliente. Além disso, o DTO especifica um parâmetro de classificação (uma enumeração amigável ao cliente).

Em um cenário simples, tudo funciona muito bem: o serviço envia expressões de paginação e classificação ao repositório e o repositório emite uma consulta eficiente ao banco de dados.

Tudo isso se quebra, no entanto, quando eu preciso classificar os valores gerados na memória do meu modelo de domínio. Por exemplo, a classe Product possui um método IsExpired () que retorna um bool com base na lógica de negócios. Agora não consigo classificar e paginar no nível do repositório - tudo isso seria feito na memória (ineficiente) e meu serviço precisaria saber os meandros de quando emitir esses parâmetros para o repositório e quando realizar a classificação / paginação em si.

O único padrão que parece fazer sentido para mim é armazenar o estado da entidade no banco de dados (tornar IsExpired () um campo somente leitura e atualizá-lo pela lógica do domínio antes de salvar). Se eu separar essa lógica em um repositório separado "modelo de leitura / dto" e "relatório", estou tornando meu modelo mais anêmico do que gostaria.

BTW, todos os exemplos que eu vi por aí em cálculos como esse realmente parecem se apoiar no processamento e na memória da memória sobre o fato de que é muito menos eficiente a longo prazo. Talvez eu esteja otimizando prematuramente, mas isso não parece certo comigo.

Eu adoraria ouvir como os outros lidaram com isso, pois tenho certeza de que é comum em quase projetos envolvendo DDD.

drogon
fonte

Respostas:

3

Eu não acho que ter dois modelos de domínio diferentes do mesmo modelo de dados torne seu domínio anêmico. Um modelo de domínio anêmico é aquele em que a lógica de negócios que muda frequentemente fica oculta do domínio em uma camada de serviço (ou, pior, na camada da interface do usuário).

A separação dos modelos de domínio de comando e consulta é adotada com frequência e possui um acrônimo interessante que você pode pesquisar no CQRS (Segregação de responsabilidade de consulta de comando).

Empregando o padrão de modelo de domínio, Udi Dahan

Embora eu tenha sido "bem-sucedido" no passado na criação de um único modelo de objeto persistente que lidava com comandos e consultas, muitas vezes era muito difícil dimensioná-lo, pois cada parte do sistema o puxava em uma direção diferente.

Acontece que os desenvolvedores geralmente assumem requisitos mais árduos do que os negócios realmente precisam. A decisão de usar as entidades do modelo de domínio para mostrar informações ao usuário é apenas um exemplo.

[...]

Para aqueles com idade suficiente para se lembrar, as práticas recomendadas ao usar o COM + nos orientaram a criar componentes separados para lógica somente leitura e lógica de leitura e gravação. Aqui estamos, uma década depois, com novas tecnologias como o Entity Framework, mas esses mesmos princípios continuam mantendo.

CQRS com atores Akka e modelos de domínio funcional, Debasish Ghosh

Greg Young realizou ótimas sessões sobre DDD e CQRS. Em 2008, ele disse que "um único modelo não pode ser apropriado para relatórios, buscas e comportamento transacional". Temos pelo menos dois modelos - um que processa comandos e alimenta alterações em outro modelo que atende a consultas e relatórios de usuários. O comportamento transacional do aplicativo é executado por meio do rico modelo de domínio de agregados e repositórios, enquanto as consultas são atendidas diretamente a partir de um modelo de dados não normalizado.

Martin Fowler CQRS

A mudança introduzida pelo CQRS é dividir esse modelo conceitual em modelos separados para atualização e exibição, aos quais ele se refere como Comando e Consulta, respectivamente, seguindo o vocabulário de CommandQuerySeparation. A lógica é que, para muitos problemas, principalmente em domínios mais complicados, ter o mesmo modelo conceitual para comandos e consultas leva a um modelo mais complexo que não se sai bem.

Em resumo, sua idéia de ter o modelo de comando manipular a expiração e transmiti-la ao banco de dados é absolutamente boa. Leia o primeiro artigo acima e você verá cenários semelhantes, porém mais complexos.

pdr
fonte
2

ESPECIFICAÇÃO

Sei que você já aceitou uma resposta, mas perguntou sobre DDD, e a correspondência exata para isso é o que Evans chama de 'especificação':
link direto do google books
Se esse link não funcionar, verifique o livro nesses resultados
É a página 226 se você tiver o livro.

Na página 227, existem 3 usos para especificações: Validação, Seleção, Criando um novo objeto especial. A sua é 'seleção' - IsExpired.

Outra coisa sobre o conceito de 'especificação' é que ele admite que - por uma questão de eficiência - você pode precisar de uma versão do código para operar nos objetos na memória e outra versão do código para consultar com eficiência o repositório sem precisar primeiro obter todos os objetos na memória.

Em um mundo simples, isso significaria colocar uma versão SQL em seu repositório e uma versão de objetos em seu modelo, é claro que possui desvantagens. A lógica está em 2 locais (ruim, alguém vai esquecer de atualizar esses locais) e existe uma lógica de domínio no seu repositório.

Portanto, a resposta é colocar os dois conjuntos de lógica em uma especificação. A versão na memória, obviamente, mas também uma versão do repositório. Se você estiver usando, por exemplo, n-hibernate, poderá usar sua linguagem de consulta interna para a versão do repositório.

Caso contrário, você precisará criar um método de repositório especial para essa especificação que é usado no objeto de especificação. As solicitações de coleções de objetos correspondentes à especificação passariam pela especificação, não pelo repositório. E pelo menos o código grita 'eu estou em dois lugares, não esqueça' para futuros mantenedores. Há um exemplo maravilhoso na página 231-232 para a solução de um problema muito semelhante.

A especificação é um vazamento / escorregamento 'permitido' da 'pureza' do DDD. Ainda pode não atender às suas necessidades para diversos propósitos. Por exemplo, o ORM pode gerar SQL incorreto; pode haver muita codificação extra. Portanto, você pode precisar chamar métodos de repositório, de modo que seja quase como colocar SQL na especificação. Uma coisa ruim, é claro. Mas não se esqueça, seu programa precisa funcionar a uma velocidade razoável. Não precisa ganhar um prêmio de pureza DDD. Portanto, a realidade da troca de datastores pode significar cirurgias à moda antiga durante todo o programa. Também é uma coisa ruim. Mas não é tão ruim quanto um programa lento (também conhecido como SUCKing). Se ficar inoperante em vários bancos de dados for uma realidade, obviamente, você duplicará as regras de negócios para cada armazenamento de dados para cada especificação. Pelo menos você está de olho no assunto e pode usar o padrão de estratégia ao trocar de repositório. Mas se você estiver usando um banco de dados específico, lembre-seYAGNI.

Em relação ao CQRS: A citação de Fowler por pdr acima ainda é verdadeira aqui: "ter o mesmo modelo conceitual para comandos e consultas leva a um modelo mais complexo que não se sai bem" ... e você pode precisar usar o CQRS ou semelhante. Mas é muito mais caro do ponto de vista de desenvolvimento e manutenção. Se você é um fornecedor de pacotes em concorrência com outras pessoas, pode pagar. Se você estiver escrevendo um aplicativo LOB personalizado para um cliente, fotografar com perfeição é uma má escolha. Você precisa decidir se o valor de ter um modelo completo ou quase o dobro vale o esforço extra. Especificaçãoé um bom compromisso, pois permite que você faça essa separação em apenas uma pequena parte do programa que precisa, com a velocidade (desenvolvimento) e a simplicidade de um modelo. Boa sorte!

FastAl
fonte
Isso faz todo o sentido. Acho que preciso morder a bala e ler o livro de Evans :-) Estou vendo agora que ter uma compreensão superficial desses conceitos pode realmente paralisá-lo!
drogon
0

Acho que questionaria qual é a lógica de negócios que determina se isExpired é verdadeiro ou não. Essa lógica pode ser executada por uma consulta como o modelo de dados permanece? Nesse caso, você pode tornar seu repositório inteligente o suficiente para usar a lógica "isExpired" quando solicitar produtos de uma determinada maneira? Caso contrário, talvez você precise reexaminar seu modelo de dados.

DDD não significa que seus repositórios precisam ser estúpidos - apenas significa que seu domínio precisa saber como conversar com seus repositórios.

Matthew Flynn
fonte