Design orientado a domínio e interação entre domínios

10

Eu sou um novato em DDD, mas estou lendo tudo o que posso colocar em minhas mãos para ferver e destilar meus conhecimentos.

Me deparei com esta pergunta DDD, e uma das respostas me intrigou.

Contextos e domínios vinculados por DDD?

Em uma das respostas, o pôster mostra o exemplo de um sistema de comércio eletrônico com produtos em pelo menos dois domínios:

1) Catálogo de produtos 2) Gerenciamento de estoque

Tudo bem, tudo faz sentido, ou seja, no seu front end de comércio eletrônico, você está interessado em exibir as informações do produto e não em gerenciamento de inventário.

MAS. Você pode exibir o nível de inventário na página da Web ou exibir o número de edição do inventário em estoque (imagine que seu inventário seja livros, revistas etc.). Essas informações são do domínio Inventário.

Então, como você lidaria com isso? Você iria

a) Carrega os domínios de produto e de inventário agregados? b) Você manteria algumas propriedades em sua entidade de domínio do Produto para número em estoque e edição em estoque e usaria Eventos de Domínio para atualizá-las quando a entidade Inventário for atualizada?

Uma pergunta final. Eu sei que devemos esquecer / ignorar a persistência do domínio e apenas pensar no domínio. Mas, só para pensar nisso, no exemplo acima, teríamos potencialmente 2 tabelas de banco de dados para catálogo e inventário de produtos. Agora, usamos o mesmo identificador neles, pois é o mesmo produto. Ou, podemos usar 1 tabela e 1 linha para os dados e simplesmente mapear os dados relevantes nas propriedades agregadas?

PendorPaul
fonte

Respostas:

8

Você pode exibir o nível de inventário na página da Web ou exibir o número de edição do inventário em estoque (imagine que seu inventário seja livros, revistas etc.). Essas informações são do domínio Inventário.

O principal a ser observado neste momento é que você está falando sobre uma exibição, ou seja, o uso de dados obsoletos é aceitável.

Dito isto, você não precisa interagir com os agregados (responsáveis ​​por impedir que alterações violem os invariantes dos negócios), mas com uma representação de uma cópia recente do estado do agregado.

Então, o que eu normalmente esperaria é uma consulta executada no Catálogo de Produtos e outra no Inventário, e algo para compor os dois no DTO que você precisa para dar suporte à exibição.

Carregar os domínios de produto e de inventário?

Então está perto . Não precisamos carregar os agregados, porque não vamos mudar nada. Mas precisamos do estado deles; para que pudéssemos carregar isso. Dito isso, eu normalmente esperaria que os dois domínios estivessem sendo executados em processos diferentes. Portanto, estaríamos chamando os dois, não carregando os dois.

Você manteria algumas propriedades em sua entidade de domínio do Produto para número em estoque e edição em estoque e usaria Eventos de Domínio para atualizá-las quando a entidade Inventário for atualizada?

"Não atravesse as correntes. Seria ruim."

Usando eventos para coordenar informações entre contextos de domínio: ótima idéia. Empurrando conceitos que pertencem a um domínio para outro: o oposto de uma ótima idéia, exceto mais.

Você deseja manter os domínios limpos. Os aplicativos que interagem com os domínios não são tão importantes. Por exemplo, é razoável que o aplicativo Inventory chame um serviço no aplicativo do produto para consultar alguns conceitos específicos do produto para adicionar a uma exibição. Ou vice-versa.

Não sei por que motivo um único aplicativo precisa ser restrito a um único domínio. Enquanto houver uma única fonte de verdade, você poderá distribuir as transações da maneira que desejar.

Mas, só para pensar nisso, no exemplo acima, teríamos potencialmente 2 tabelas de banco de dados para catálogo e inventário de produtos. Agora, usamos o mesmo identificador neles, pois é o mesmo produto.

Essa seria a maneira mais fácil. Em termos maiores, você usa o mesmo identificador porque a entidade do mundo real é a mesma; os dois contextos limitados diferentes modelam essa entidade de maneira diferente, mas o modelo não é a entidade do mundo real.

Quando isso não funcionar, você precisará de algumas consultas para preencher a lacuna. Eu acho que a variação mais comum disso é que a entidade mais nova preserva o id da entidade mais antiga. Você também verá isso em um único BC: os candidatos, quando aprovados, tornam-se clientes. É um agregado diferente (o estado associado a um cliente está sujeito a uma invariante diferente da do candidato); portanto, se sua camada de persistência estiver usando fluxos de eventos, o fluxo para o novo agregado precisará de um identificador diferente. Portanto, haverá um pouco de estado em algum lugar que diga "esse candidato se tornou esse cliente".

Ou, podemos usar 1 tabela e 1 linha para os dados e simplesmente mapear os dados relevantes nas propriedades agregadas?

CARAMBA! Não faça isso. Você está adicionando contenção de transações sem qualquer motivo comercial para isso.

VoiceOfUnreason
fonte
Marquei isso como resposta, e agradeço também a @ guillaume abaixo, que também apontou que a leitura dos dados a serem exibidos em uma exibição não requer o carregamento dos agregados. Obrigado por uma resposta tão longa e detalhada. Vindo da primeira abordagem "tradicional" do modelo de dados, achei difícil me forçar a esquecer a camada de persistência e me concentrar na linguagem do domínio. Justamente quando acho que entendi, li outro artigo que deixa meu entendimento à parte.
precisa saber é o seguinte
Acabei de ler este artigo, msdn.microsoft.com/en-us/magazine/dn802601.aspx, que detalha o uso de Eventos de Domínio para duplicar alguns dados entre contextos. O exemplo é o compartilhamento de uma lista de clientes entre o atendimento ao cliente e um sistema de processamento de pedidos. Isso está duplicando os dados do cliente no sistema de pedidos e usando Eventos para sincronizar os dados. Isso vai contra o que respondemos aqui. Certamente, na amostra vinculada, o contexto do pedido precisa apenas de um ID do cliente e isso pode ser preenchido a partir do aplicativo que tem acesso ao contexto do serviço ao cliente.
precisa saber é o seguinte
Você vai querer ser muito preciso em seu pensamento, aqui. Copiar dados entre sistemas NÃO é o mesmo que copiar dados entre modelos de domínio. Sempre que você vê "somente leitura [murmurar]", é uma grande dica de que os dados não pertencem a esse domínio (mesmo que o aplicativo ainda se importe com isso).
VoiceOfUnreason
Sim, foi o que pensei também. A mudança do processo de pensamento é bastante difícil sem "especialistas" publicando artigos que turvam as águas. Passei hoje analisando realmente meu domínio, para tentar entender completamente onde estão minhas raízes BC e Aggregate. Estou praticamente lá, mas também sei que posso refinar e refatorar meu modelo à medida que vou. Estou preso no inferno da análise há dias, preocupa-me em começar a escrever código com medo de não ter DDD direto na minha cabeça. Eu acho que é melhor quebrar, continuar questionando meu código e se o modelo está correto. Eu chegarei lá. Graças
PendorPaul
Talvez eu tenha interpretado mal, mas a prática com a qual acredito que você está falando se chama Transferência de estado transportada por evento (transferência de estado transportada por evento) e pode ser um modelo muito poderoso, especialmente para exibições de painel / tabela em que você precisa filtrar e paginar os dados entre serviços. Você pode criar "projeções" (basicamente visualizações) de eventos do domínio para impulsionar esses painéis. Eu o usei antes com bastante sucesso. Pode ser uma ferramenta muito poderosa no cenário certo. O mais importante aqui é que você percebe que essas projeções não representam modelos de domínio e que não devem conter lógica de negócios.
Jordan
3

Acho que sua pergunta realmente exige 2 conjuntos ortogonais de opções -

  • Você carrega dois objetos e apresenta seus dados juntos ou carrega 1 objeto que contém tudo o que deseja?

  • Você usa agregados para exibir coisas ou algo mais?

Se você acredita na abordagem CQRS, os agregados podem não ser a melhor aposta para as leituras. Sempre que você carrega um agregado, seja para exibir ou modificar seus dados, você adiciona simultaneidade e contenção ao seu sistema. Além disso, os agregados são potencialmente mais volumosos e mais lentos do que se você usasse modelos de leitura ad-hoc personalizados para exibição.

A solução a) do seu Q parece sujeita a muitas dessas armadilhas. A opção b) pode ser válida, mas eu a usaria apenas se dados do InventoryManagementBC fossem necessários para impor invariantes ao alterar o Productagregado. É melhor que um agregado contenha todos os dados necessários para verificar suas regras de negócios após a modificação, mas no lado da leitura eles podem ficar em qualquer lugar.

Em relação aos dados, uma recomendação comum é fornecer aos contextos vinculados seu próprio banco de dados (por motivos de implantação e SoC). Você provavelmente terá que usar os mesmos identificadores se quiser combinar produtos entre os dois BCs.

Sobre interações entre BC, você também pode consultar /programming/16713041/communicating-between-two-bounded-contexts-in-ddd

guillaume31
fonte
11
OK, isso ajuda. Basicamente, estamos usando Raízes Agregadas e BC para garantir que nossos invariantes sejam consistentes e nossos dados sejam válidos e as operações que desejamos executar sejam agrupadas em nosso agregado ou BC. Quando se trata de ler e exibir esses dados, podemos lidar com isso separadamente e com pouco peso, sem que todos os agregados / BC sejam hidratados. Afinal, por que precisamos carregar o Agregado quando tudo o que estamos fazendo é exibir os dados em um relatório ou na tela. Isso faz todo o sentido. Obrigado.
precisa saber é o seguinte
É aqui que o CQRS realmente brilha. Você pode simplesmente fazer uma consulta SQL, ingressar nas tabelas e retornar a consulta personalizada para uma visualização específica. Ele também limpa seu repositório da bagunça do método de consulta. Além disso, você pode até replicar dados em serviços como o ElasticSearch e consultá-los.
doesnotmatter
1

DDD é destinado a aplicativos em que a lógica de negócios é complexa. "imprimir algo" não é uma lógica de negócios complexa. Na verdade, não é uma lógica de negócios.

Se a lógica de negócios em um contexto precisar de algumas informações para lidar adequadamente com alguns casos de uso, essas informações farão parte desse contexto. Portanto, a ideia de que um contexto limitado possa precisar de informações disponíveis em diferentes contextos limitados não faz sentido, porque o contexto limitado possui todas as informações necessárias.

Eufórico
fonte
OK, então escolha algo como a Amazon, que é um sistema complexo com lógica de negócios complexa. Eles têm gerenciamento de catálogo e gerenciamento de inventário. Eles não precisam de totais de inventário para gerenciar o catálogo; com isso quero dizer o nome do produto, descrição, tipo, condição etc. No entanto, eles mostram o número de itens em estoque na página inicial da loja. Nesse cenário, onde você deseja separar os domínios Gerenciamento do catálogo de produtos e Inventário do produto, mas precisa exibir algumas informações sobre o inventário na página do produto, como fazer isso?
precisa saber é o seguinte
Acho que o que estou dizendo é que o domínio do Catálogo de produtos tem todas as informações necessárias para gerenciar o catálogo de produtos. O domínio Inventário possui todas as informações necessárias para gerenciar o inventário. No entanto, quando eu quero exibir algumas informações para um usuário, e esses dados vêm de 2 domínios, onde eu faço isso. Apenas carrego os dois domínios na minha interface do usuário e vinculo as propriedades que quero mostrar? Ou tenho algum mecanismo de relatório / leitura em que retorno um tipo anônimo ou DTO contendo os dados necessários para minha interface do usuário?
precisa saber é o seguinte
É cômico olhar para esses meus comentários antigos há 11 meses, mas parece uma vida inteira. Você estava completamente certo de euforia quanto ao aspecto de ler e escrever. O aplicativo em que estou trabalhando evoluiu bastante à medida que meu entendimento evoluiu. Agora estou usando métodos CQRS, através de Jimmy Bogards Mediatr. A pura flexibilidade de ter comandos que interagem com os agregados, mas o uso de consultas e manipuladores de consultas para trazer de volta o que eu preciso para exibição é incrível. Embrulhe isso em Views que chamam esses QueryHandlers, e a dissociação é boa com esse. Graças
PendorPaul
@PauloPaul, acho que estou onde você estava há 11 (agora 13) meses atrás. O que levou você aonde está agora?
Greg Sino
11
@ GregBell Você precisa forçar uma mudança de mentalidade. Eu estava preso na abordagem de "projetar um banco de dados, criar uma camada de dados, construir alguma lógica de negócios ... etc". E eu estava focado em criar todas as entidades abrangentes. ou seja, um "Produto" em um site de comércio eletrônico, que lidava com tudo, desde preços, descrição, inventário, localização de estoque ... mas isso se torna extremamente complexo. A abordagem do contexto limitado significa que, no contexto do Inventário, o "Produto" contém apenas informações e comportamento para o gerenciamento de inventário. Descrição e imagens são gerenciadas em um contexto de conteúdo, com preços no contexto de preços.
PendorPaul
1

Do meu ponto de vista, existem definições diferentes de "Produto" - todo contexto delimitador possui sua própria definição de domínio "produto":

  • No Contexto de Limite de Gerenciamento de Conteúdo, um produto possui uma imagem e um texto de descrição.
  • No contexto de limite de estoque, um produto possui quantidades em estoque, vendedor do produto, impede quando o produto estará disponível
  • No contexto de preço-caculação-limite, existem regras quanto um produto pode custar por quantidade.

Além disso, eu adicionaria um contexto adicional de delimitação de loja com sua própria definição de produto (uma combinação relevante dos domínios de produto dos outros contextos de delimitação).

Um produto da loja teria "imagem e texto de descrição" do conteúdo e disponibilidade do "inventário", mas não "vendedor do produto" do inventário.

Esse contexto adicional de delimitação de loja depende do conteúdo, do inventário e do preço do contexto de delimitação

k3b
fonte
Então, como você está criando esse Shop-Product BC? Nesse contexto, você tem uma referência ao Produto e ao Inventário BC e está hidratando os da sua loja de persistência ao carregar um Store-Product BC e, em seguida, oferece as propriedades desejadas desses BCs através das propriedades StoreProduct? Eu encontrei este artigo, na verdade, que está na linha do que eu estava pensando, cruza os eventos do BC, mas o @ guillaume13 acima apontou que, para fins de exibição, posso evitar o BC e retirar os dados necessários para a minha visão. Em
seguida