Como projetar os limites agregados?

10

Eu gostaria de escrever um aplicativo parecido com comércio eletrônico.

E você sabe que em aplicativos semelhantes, os produtos podem ter propriedades e recursos diferentes. Para simular essa oportunidade, criei as seguintes entidades de modelo de domínio:

Categoria - é algo como "eletrônicos> computadores", ou seja, tipos de produtos. As categorias contêm uma lista de propriedades (Lista <Propriedade>).

Entidade independente da propriedade que contém o nome, unidades de medida, tipo de dados. Por exemplo "nome", "peso", "tamanho da tela". A mesma propriedade pode ter produtos diferentes.

Produto - contém apenas o nome e a lista de valores relacionados às propriedades. Valor é um objeto que contém apenas o campo de valor e a identificação do campo da propriedade.

Decidi originalmente tornar a categoria como agregada única nesse esquema, porque, por exemplo, quando adiciono um novo produto, preciso conhecer todos os dados relacionados à categoria atual, incluindo propriedades relacionadas à categoria atual ( category.AddNewProduct (product) ). Mas o que devo fazer quando só precisar adicionar uma nova propriedade que não pertença a nenhuma categoria. Por exemplo, não posso fazer esta categoria. AddNewProperty (propriedade) porque diz claramente que adicionamos a propriedade a uma categoria específica.

Ok, no próximo passo eu decidi separar Property em um agregado separado, mas então será uma lista com entidades simples.

É claro que posso criar algo como PropertyAggregate para manter uma lista interna de propriedades e regras de negócios, mas quando adiciono um produto, preciso ter dentro da categoria a lista inteira de propriedades pertencentes a essa categoria para verificar os invariantes. Mas também estou ciente de que manter os links dentro do agregado em outros agregados é uma prática ruim.

Quais são as opções para projetar esse caso de negócios?

cephei
fonte
Você poderia fornecer um exemplo mais completo de uma categoria, propriedade e produto? Eletrônicos ou computadores seriam uma categoria, o iPhone X seria um exemplo de produto e uma propriedade seria o que exatamente? Ecrã de 11 polegadas?
Neil
você está quase certo. Eu adicionei alguns esclarecimentos
cephei
Parece que você está olhando para o design agregado exclusivamente de uma perspectiva de "contêiner de dados". Você também pode querer pensar sobre casos de uso de sua aplicação, tendo em conta aspectos transacionais, colaboração / acesso simultâneo, eventos que ocorrem, as transições de estado, etc.
guillaume31

Respostas:

7

Na perspectiva DDD, Category, Producte Propertysão entidades: todos eles correspondem a objetos que têm sua própria identidade.

Opção 1: seu design original

Você criou Categorya raiz de um único agregado. De um lado, isso faz sentido, porque o agregado assegura a coerência quando seus objetos são modificados, e Productdeve ter a Propertiesde seu Category:

insira a descrição da imagem aqui

Mas, por outro lado, o agregado único significa que todos os seus objetos estão relacionados a uma raiz que os possui e todas as referências externas devem ser feitas através dessa raiz agregada. Isso implica que:

  • um específico Productpertence a um e apenas um Category. Se o Categoryé excluído, também o é Products.
  • um específico Propertypertence a um e apenas um Category. Caso contrário, se "telas de TV" e "monitores de computador" tiverem duas categorias, "telas de TV: tamanho" e "monitores de computador: tamanho" seriam duas propriedades diferentes.

O segundo ponto não corresponde à sua narrativa: " Mas o que devo fazer quando só preciso adicionar um novo Propertyque não pertença a nenhuma categoria ". E não está claro se o mesmo Propertiespode ser usado em diferentes Categories.

Opção 2: Propriedade fora do agregado

Se um Propertyexistir independentemente do Categories, ele deve estar fora do agregado. E o mesmo se você quiser compartilhar Propertiesentre Categories(o que faz sentido para altura, largura, tamanhos, etc ...). Este parece definitivamente ser o caso.

A conseqüência está no link entre Propertye as coisas que pertencem ao agregado: enquanto você pode navegar do interior do agregado para Property, não é mais permitido ir diretamente de a Propertypara os valores correspondentes. Essa restrição de navegabilidade pode ser mostrada em um diagrama UML:

insira a descrição da imagem aqui

Observe que esse design não impede que você List<Property>entre Category, com uma semântica de referência (por exemplo, java): cada referência na lista se refere a um Propertyobjeto compartilhável em um repositório.

O único problema com esse design é que você pode alterá- Propertylo ou excluí-lo: como está fora do agregado, o agregado não pode cuidar da consistência de seus invariantes. Mas isso não é um problema. É a conseqüência dos princípios DDD e da complexidade do mundo real. Aqui está uma citação de Eric Evans em seu livro seminal " Design Orientado a Domínio: Enfrentando a Complexidade no Coração do Software ":

Não se espera que nenhuma regra que abranja AGGREGATES esteja atualizada o tempo todo. Por meio do processamento de eventos, processamento em lote ou outros mecanismos de atualização, outras dependências podem ser resolvidas dentro de um tempo especificado. Mas os invariantes aplicados em um AGGREGATE serão aplicados com a conclusão de cada transação.

Portanto, sim, se você alterar a Property, precisará garantir que um serviço verifique as categorias referentes a ele seja atualizado conforme necessário.

Opção 3: Categoria, Propriedade e Produto em diferentes agregados

Eu apenas me pergunto se a suposição de que a Productpertence a um single Categoryé fundada:

  • Frequentemente, vejo lojas online propondo uma Productem várias Categories. Por exemplo, você encontrará "Laptop Marca X Modelo Y" na categoria "Laptops" e na categoria "Computadores" e uma "impressora multifuncional Z" na categoria "impressora", "scanner" e "fax".
  • Não é possível que alguém crie um Productprimeiro e só depois o atribua a Categorias e preencha os valores?
  • Se você deseja dividir uma categoria, você realmente exclui seus produtos e os recria sob as novas categorias?

Não simplificará as agregações, e você terá ainda mais regras que abrangem agregações. Mas seu sistema seria muito mais à prova de futuro.

Christophe
fonte
Muito obrigado, esta é uma explicação muito útil. Mas gostaria de esclarecer alguns pontos. Gostaria de começar com a segunda opção e, quem sabe, talvez eu chegue à terceira. Se eu Propertyultrapassar os limites do Categoryagregado, isso significa que ele Propertyse torna agregado e precisa de um repositório? Se for verdade, como passar o requisito List<Property>para a Categoryinstância? Através do construtor? Vai dar certo? E como faço para descobrir a lista deProperty IDs de um Categoryque ainda não foi criado?
Cephei
@ zetetic em resumo: sim, você precisará de um repositório de propriedades independente. Você passa uma lista de propriedades existentes para a fábrica da categoria ou cria categorias vazias e preenche a lista com um método addProperty. Pergunta em troca: imagine que você deseja ter propriedades "obrigatórias" e "opcionais", e a característica obrigatória depende da categoria. Como você lidaria com isto ?
Christophe
Ao responder sua pergunta, a primeira coisa que me vem à mente é que eu posso criar uma entidade especial Featuree ela pertencerá apenas aoProduct . e essa entidade não participará da pesquisa. O que você disse ?
Cephei
@ zetetic porque não! Eu teria deixado os Valores como estão atualmente no produto e associaria o recurso à categoria. Uma categoria possui n recursos (parte de seu agregado), uma Propriedade define m Recursos (mas o link passa pelo recurso Categoria->). Você então decompôs o relacionamento muitos para muitos em elementos mais gerenciáveis, esclarecendo o limite agregado. Finalmente sobre a injeção de repositório: isso não é necessário se você faz referência outros agregados de identidade (ler este artigo informit.com/articles/article.aspx?p=2020371&seqNum=4 )
Christophe
5

A meu ver, você pode resolver isso de duas maneiras:

Categoria é um tipo especial de produto

Isso significa que, para qualquer produto em seu banco de dados, ele contém uma chave estrangeira apontando para o mesmo produto da tabela. Um produto é um produto apenas se não existirem produtos cuja chave estrangeira seja igual à identificação do referido produto. Em outras palavras, se ele não possui produtos, é um produto.

Isso simplificaria um pouco as coisas. Os produtos para propriedades teriam um relacionamento um para muitos e, portanto, suas categorias também terão um relacionamento para muitos, pois também são produtos. Adicionar uma propriedade a uma categoria seria tão fácil quanto adicionar uma propriedade a um produto no seu programa. Carregar todas as propriedades significaria combinar as propriedades do produto com as propriedades do produto de categoria associado e até chegar a um produto de categoria sem pai.

Seu aplicativo de comércio eletrônico precisaria fazer essa distinção, mas se você provavelmente carregar produtos de uma categoria de qualquer maneira, não será uma perda de desempenho saber se você está lidando com uma categoria ou um produto. Também se presta a pesquisar na forma de árvores ao nível do produto, pois cada produto (categoria) abriria uma lista de subprodutos sem muito trabalho adicional.

A desvantagem disso é, obviamente, as informações extras presentes no produto que não fazem sentido para uma categoria criariam campos não utilizados no produto. Embora essa solução seja mais flexível em seu aplicativo, também é um pouco menos intuitiva.

Relacionamento muitos-para-muitos

Os produtos não estão mais em um relacionamento composto com a propriedade. Você cria uma tabela ProductProperty com chaves estrangeiras da tabela do produto e da tabela de propriedades que vinculam as duas. Da mesma forma, você tem uma tabela de categorias com um relacionamento muitos para muitos com a tabela de propriedades e uma tabela CategoryProperty com chaves estrangeiras da tabela de categorias e da tabela de propriedades.

O produto em si teria um relacionamento muitos-para-um com a categoria, permitindo que você crie essencialmente uma lista de propriedades exclusivas pertencentes ao produto e à categoria por meio de uma instrução de seleção bem formalizada.

Do ponto de vista do banco de dados, isso é definitivamente mais limpo e mais flexível. Provavelmente, seu aplicativo pode funcionar sem a necessidade de lidar diretamente com CategoryProperty ou ProductProperty se a consulta for feita corretamente. No entanto, você também não deve tratar a categoria ou o produto como proprietário da propriedade. Deve ser sua própria entidade dentro do seu programa. Isso também significa que o gerenciamento das referidas propriedades seria uma questão de criar a própria propriedade e associá-la a uma categoria ou produto em duas etapas separadas. Certamente mais trabalho que a primeira solução, mas de nenhuma maneira mais difícil.

Além disso, você também teria que executar uma verificação adicional ao excluir a categoria ou o produto se alguma de suas propriedades estiver sendo usada por outras pessoas (diferente da primeira solução em que você pode eliminar com segurança todas as propriedades associadas de um determinado produto / categoria) .

Conclusão

Em um contexto profissional, eu iria além da categoria de milhas e distâncias entre produtos e produtos e propriedades, usando a abordagem muitos para muitos. Não haveria possibilidade de sobreposição de dados e, em certo sentido, é mais fácil raciocinar cada um desses três como sua própria entidade. No entanto, de maneira alguma a primeira solução é ruim, pois também permite que você escreva um aplicativo mais simples. Apenas saiba que, se você pensou que poderia eventualmente mudar de uma solução para outra, provavelmente seria do seu interesse escolher a segunda.

Boa sorte!

Neil
fonte
Obrigado pela resposta detalhada e interessante! no nível do banco de dados que eu já modelei como você explicou no segundo caso, esse padrão é chamado de entidade-atributo-valor, mas estou preso no nível do código, a definição de agregados. Na maioria dos casos, todas essas entidades são usadas juntas. é possível combinar em um agregado, mas há casos que gostam de preencher diretórios que, por sentido, são eliminados do agregado.
Cephei