Como trabalhar com grandes raízes agregadas?

11

Estou aprendendo DDD e ainda tenho mais perguntas do que respostas.

Vamos considerar um modelo de diretório que contém um número enorme de arquivos.
Aqui está como eu o vejo:

O diretório é uma raiz agregada.
Essa entidade deve ter a lógica de validação para verificar a exclusividade do nome do arquivo quando ele é adicionado ou apenas renomeado. E a entidade File contém a lógica 'SetName', notificando o Diretório via Evento de Domínio sobre alterações de nome.
Mas como o Directory deve funcionar?
Nem sempre é possível carregar todos os arquivos na memória. Nesse caso, o repositório de arquivos deve ter uma lógica adhoc para verificar a exclusividade do nome? Suponho que é uma decisão viável.
No entanto, e se alguns arquivos já tiverem sido adicionados ou renomeados na transação atual ainda não confirmada? (nada proíbe isso. Os limites da transação são definidos externamente em relação à lógica de negócios). Provavelmente, o repositório deve levar em consideração os estados persistentes e na memória (mesclar esses estados pode ser uma tarefa não trivial).

Então, quando a raiz agregada com todos os seus filhos se encaixa na memória - está tudo bem. E assim que você não puder materializar todas as entidades, haverá problemas.

Eu gostaria de saber quais são as abordagens para essas situações. Pode não haver nenhum problema e é apenas por causa do meu mal-entendido sobre o assunto.

Pavel Voronin
fonte
1
O que faz você pensar que precisa carregar todos os arquivos e seu conteúdo, e não apenas "FileInfo"?
Euphoric
@Eufórico. Bem, às vezes até isso não é possível. Enfim, há outro problema. Como fornecer consistência entre FileInfo e as entidades de arquivo correspondentes alteradas dentro da transação atual? Provavelmente o CQRS aborda essa questão ... ainda não a analisamos.
Pavel Voronin
1
É útil entender que o DDD não é uma técnica de programação, mas uma técnica de design. Muitas pessoas o tratam como uma metodologia de codificação. Termos como "raiz agregada" agravam o problema, porque dão a impressão de peso técnico, quando na verdade não falam muito sobre técnicas de programação. Técnicas de programação não mudam muito no DDD; enquanto o DDD informa seu código e arquitetura, você ainda tem código e arquitetura separados dele.
Robert Harvey
@RobertHarvey Parece-me que o DDD requer técnicas de programação mais complexas. Pelo menos quando se trata de casos de canto. Trato o DDD principalmente como uma maneira de separar (e localizar) a lógica de negócios do código de infraestrutura inevitável que funciona implicitamente nos bastidores. Para mim, DDD = boa OOD + infraestrutura implícita. A maioria das perguntas que tenho sobre DDD está relacionada à última parte.
Pavel Voronin
2
Por que você diz: "Os limites da transação são definidos externamente em relação à lógica de negócios"? O dever da raiz agregada é manter um limite transacional. Além disso, você não precisa carregar o conteúdo dos arquivos. Você pode apenas carregar metadados.
Esben Skov Pedersen

Respostas:

20

Minha resposta é tendenciosa com o grande livro Implementing Domain Driven Design de Vaughn Vernon (uma leitura obrigatória)

1. Favorecer agregados pequenos.

Se eu quiser modelar seu domínio, eu modelaria a Directorycomo um agregado e Filecomo outro agregado.

2. Agregados de referência por IDs.

Portanto Directory, terá uma coleção de FileIdobjetos de valor.

3. Use fábricas para criar agregados.

Para um caso simples, um método de fábrica pode ser suficiente Directory.addFile(FileName fileName). No entanto, para casos mais complexos, eu usaria uma fábrica de domínio.
A fábrica de domínio pode validar que fileNameé exclusivo usando um FileRepositorye um UniquefileNameValidatorserviço de infraestrutura.

Por que modelar Filecomo um agregado separado?

Porque Directoriesnão são feitos Files. a Fileestá associado a um certo Directory. Além disso, pense em um diretório que possui milhares de arquivos. Carregar todos esses objetos na memória toda vez que um diretório é buscado é um fator que prejudica o desempenho.

Modele seus agregados de acordo com seus casos de uso. Se você souber que nunca haverá mais do que 2-3 arquivos em um diretório, poderá modelá-los todos como um único agregado, mas, na minha experiência, as regras de negócios mudam o tempo todo e vale a pena se o seu modelo for flexível o suficiente para acomodar o alterar.

Leitura obrigatória Design agregado eficaz por Vaughn Vernon

Songo
fonte
2
Observe que, de fato, implementações de diretório real geralmente são implementadas assim (agregados separados, referenciados por seus IDs). Por exemplo, "Arquivos" são simplesmente referenciados em seus diretórios pelos inodesistemas Unix.
Alexander Langer
1
Finalmente li o livro recomendado. Ótimo livro, obrigado. No entanto, ainda não está completamente claro. Somente um Filecom determinado Nome pode existir Directory, ou seja, Directorypossui a invariante: Exclusividade do Nome. Pelo menos é importante para o Directory, mas não para o File. Na verdade, @AlexanderLanger está certo: 'Arquivo' pode ser referenciado por muitas 'Pastas'. E o nome é provavelmente a propriedade dessa referência e não a Filesi mesma. Está bem. A renomeação da funcionalidade pertence ao Directory, mas, novamente, não é uma boa ideia armazenar milhares de identidades referenciadas.
Pavel Voronin
E mesmo se o fizéssemos, precisaríamos verificar os nomes. A partir daqui, parece razoável criar um método no repositório de pastas: bool ContainsFileOfName(int folderId, string fileName). Depois disso, a assinatura do método 'Renomear' pode ser a seguinte: void Rename(int fileId, string newName)onde dentro de alguns IFolderService(que repositório envolve) é resolvido e perguntado se esse nome existe.
Pavel Voronin
1

Esta não é uma pergunta DDD propriamente dita. A principal questão aqui é sobre o contexto de sincronização (que é aqui uma raiz agregada).

Voltar ao tópico: O diretório deve bloquear algum objeto de sincronização dos nomes dos arquivos e verificar se o nome do arquivo fornecido é permitido, o que é O (n) no pior caso.

Mare Infinitus
fonte
1

Embora alguns possam dizer que tentam alterar seu design, sempre há uma necessidade de um AR ter uma grande lista de objetos simples. E armazená-los na memória não é a melhor coisa a fazer da perspectiva do desempenho, no entanto, tudo o que você precisa fazer nesses casos é preservar os limites da transação. Uma solução simples é a seguinte:

  1. Mantenha o AR da pasta simples e coloque uma coluna de versão.
  2. Faça com que cada arquivo (digamos linha) faça referência à raiz agregada, em outras palavras, para manter o ID da pasta.
  3. Sempre execute as alterações dos arquivos, como renomear, adicionar, remover, via AR. Em outras palavras, se você deseja adicionar um arquivo, carregue o AR e use um método no AR para fazer a alteração. Supondo que você esteja usando um padrão de repositório, o addFile () criará um novo arquivo, altere a versão da pasta. Salve-os como uma UoW. Se outra pessoa alterou o AR, você receberá um erro devido à Coluna da versão (a versão do AR).
  4. Qualquer alteração no próprio arquivo, como Editar ou renomear, deve ser feita via AR. Portanto, a versão é mantida no AR. Essencialmente, isso significa que não há outros caminhos de execução no seu código que alteram o arquivo, exceto o AR proprietário.

Algumas restrições:

  1. O arquivo deve pertencer a apenas um AR. Se não for esse o caso, modele a relação de Pasta -> Arquivo como contenção e não o próprio arquivo.
  2. Você não pode mover um arquivo de uma pasta para outra, a menos que faça essa alteração em uma UoW, ou seja, na mesma transação. Este é um caso especial, como você deve enviar uma solicitação que terminará em dois comandos, para que os dois ARs sejam alterados, portanto, duas novas versões para cada uma e provavelmente dois eventos (arquivo removido, arquivo adicionado) e isso é um pouco complicado porque você deve manter as ordens de eventos para os dois ARs, o que às vezes não é fácil.
ligu
fonte