Eu prefácio esta pergunta dizendo que sou relativamente novo no DDD, por isso posso estar cometendo alguns erros fundamentais aqui!
Estou trabalhando em um projeto que envolve os conceitos de Contas e Transações (no sentido financeiro). Uma conta pode ter muitas transações inseridas nela.
Parece-me que Conta e Transação são ambas Entidades e que Conta é uma raiz Agregada contendo Transações, pois uma Transação não pode existir sem a Conta.
No entanto, quando venho aplicar isso no código, encontrei imediatamente um problema. Em muitas situações, não é especialmente útil para mim ter uma lista de todas as transações em uma conta o tempo todo. Estou interessado em poder fazer coisas como calcular o saldo da conta e aplicar invariantes como um limite de crédito, mas também quero poder trabalhar facilmente com um subconjunto de transações (por exemplo, exibindo aquelas que se enquadram em um período).
No último caso, se eu estivesse usando um TransactionRepository
, poderia acessar com eficiência apenas os objetos necessários sem carregar a lista inteira (potencialmente muito grande). No entanto, isso permitiria que outras coisas além da conta funcionassem com transações, o que significa que eu quebrei o conceito de conta como raiz agregada.
Como as pessoas lidam com esse tipo de situação? Você apenas aceita as implicações de memória e desempenho de carregar um número potencialmente grande de filhos para uma raiz agregada?
fonte
tl; dr - quebre as regras, se necessário. DDD não pode resolver todos os problemas; de fato, as idéias objetivas que ele fornece são bons conselhos e um bom começo, mas escolhas realmente ruins para alguns problemas de negócios. Considere uma dica de como fazer as coisas.
Para a questão de carregar todos os filhos (transação) com o pai (conta) - Parece que você encontrou o problema n + 1 (algo para o google) que muitos ORMs resolveram.
Você pode resolvê-lo carregando preguiçosamente os filhos (transação) - somente quando necessário.
Mas parece que você já sabe disso ao mencionar que pode usar um TransactionRepository para resolver o problema.
Para "ocultar" esses dados para que apenas a Conta possa usá-los, você nem precisa armazená-los onde mais ninguém gostaria de desserializá-los, como uma tabela relacional pública. Você pode armazená-lo com o 'documento' da conta em um banco de dados do documento. De qualquer maneira, se alguém se esforçasse o suficiente, ainda conseguiria ver os dados. E 'trabalhe' com isso. E quando você não está olhando, eles vão!
Portanto, você pode configurar permissões, mas precisa executar a 'conta' como um processo separado.
O que você realmente percebe aqui é que o DDD e o uso puro do modelo de objeto às vezes levam você de volta a um canto. Na verdade, é claro, você não precisa usar a 'composição' / raiz agregada para se beneficiar dos princípios de design do DDD. É apenas uma coisa que você pode usar quando tiver uma situação que se encaixe em suas restrições.
Alguém pode dizer 'não otimize cedo'. Aqui, nesse caso, porém, você sabe a resposta - haverá transações suficientes para atolar um método que manterá todas elas para sempre com a conta.
A resposta real é começar a levantar SOA. No meu local de trabalho, assistimos aos vídeos de computação distribuída da Udi Dahan e compramos o nServiceBus (apenas nossa opção). Crie um serviço para contas - com seu próprio processo, filas de mensagens, acesso a um banco de dados de relações que só ele pode ver e ... viola, você pode codificar instruções SQL no programa e até mesmo lançar alguns scripts de transação Cobol (brincadeira) é claro), mas seriamente tem mais separação de preocupações do que o snob OO / Java mais inteligente jamais poderia sonhar.
Eu recomendaria modelá-lo bem de qualquer maneira; você pode obter os benefícios da raiz agregada aqui sem os problemas, tratando o serviço como um contexto de mini-limite.
Isso tem uma desvantagem, é claro. Você não pode apenas RPC (serviço da web, SOAP ou REST) dentro e fora de serviços e entre eles ou obter um antipadrão de SOA chamado 'o nó' devido ao acoplamento temporal. Você precisa usar a inversão do padrão de comunicação, também conhecido como 'Pub-Sub', que é como manipuladores de eventos e criadores de eventos, mas (1) entre processos (que você pode colocar em máquinas separadas se estiverem sobrecarregadas).
o problema real é que você não deseja que um serviço precise obter dados de outro serviço para "bloquear" ou esperar - você precisa disparar e esquecer a mensagem e deixar que um manipulador em outro local do programa o pegue para concluir o processamento. Isso significa que você precisa fazer sua lógica de maneira diferente. O nServicebus automatiza o padrão 'saga' para ajudar com um pouco disso, mas no final, você precisa desenvolver um estilo de codificação diferente. Você ainda pode fazer tudo, apenas tem que fazer diferente!
O livro "SOA Patterns", de Arnon Rotem-Gal-Oz, responde a muitas perguntas sobre isso. Incluir o uso de 'padrão de serviço ativo' para replicar periodicamente dados de serviços externos para o seu próprio país quando surgir a necessidade (muitos RPC seriam necessários ou o link não é confiável / não é no ecossistema de publicação / assinatura).
Apenas para visualizar, as interfaces de usuário precisam fazer RPC nos serviços. Os relatórios são gerados a partir de um banco de dados de relatórios alimentado pelos bancos de dados dos serviços. Algumas pessoas dizem que os relatórios não são necessários e que o problema deve ser resolvido de outra maneira. Seja cético em relação a essa conversa.
No final, porém, nem todas as coisas podem ser classificadas adequadamente em um único serviço. O mundo não roda em código ravioli! Então você terá que quebrar as regras. Mesmo que você nunca precise, os novos desenvolvedores do projeto farão isso quando você o deixar. Mas não se preocupe, se você fizer o que puder, os 85% que seguem as regras tornarão um programa muito mais sustentável.
Uau, isso foi longo.
fonte