DDD - Aggregates
Using Aggregees
Problemática
Num contexto de uma aplicação baseada em Integração, um código modular é um recurso fundamental para garantir a manutenção da plataforma e miticar os erros específicos de cada componente.
Devido a isso, a aplicação utiliza muito de Eventos, desta forma invertendo a dependência das chamadas, e permitindo estender finalidades sem editar a regra de negócio já existente.
O problema está que, para recursos fora dos componentes, mais informações seriam necessárias.
Por exemplo, quando um evento de nova mensagem criada é disparado, é fundamental que contenha informações sobre o chat em questão, seria ótimo se essa informação já estivesse resolvida.
Gerenciar todos esses dados, com recursos externos tendo que buscar detalhes sobre cada item gera um possível acoplamento entre módulos, que é indesejável neste contexto.
Solução
a solução foi simples, criar Aggregates, classes que para encapsular todos os dados de um determinado domínio em um único objeto.
Isso significa o evento de nova mensagem criada trará todo o contexto da mensagem, e não apenas os Ids para referência. Lógico que não devem conter todos os dados de referência, somente os relacionados e que façam sentido ao caso de uso de mensagem.
O Aggregate MessageAggregate contara com todos os dados de mensagem somado com ALGUNS dados de chat já resolvidos. Isso significa que em vários casos será retornado informações que não serão utilizadas, mas é um preço a se pagar. Estratégias como Cache podem mitigar esses problemas.
Aggregates devem ser usados quando um recurso é necessário FORA DE SEU MÓDULO, então, em casos de uso que não são diretamente requisitados pelo módulo.
um pequeno exemplo:
O seguinte caso é uma exemplificação do que esse recurso significa. Os payload a seguir são eventos enviados via Pub Sub para serviços inscritos, podendo ser fora de seus respectivos módulos.
Antes
Message.json
{
"Id": "message-id",
"content": "Hi!",
"CratorId": "user#1",
"ChatId": "chat#1"
}
Este exemplo está bem “Normalizado” se for levado em consideração a normalização de bancos de dados relacionados.
Esta representação está boa, caso seja usada em uma camada de implementação, e não seja a de Domínio.
Veja que, para cada serviço que precisar de mais contexto da mensagem, serão necessárias pelo menos 2 requisições. Em um sistema com bastante eventos pode ser um problema, tanto em consultas em bancos de dados, quando acoplar um recurso de SELECT a outro módulo.
Depois
MessageAggregate.json
{
"Id": "message-id",
"content": "Hi!",
"Creator": {
"Id": "user#1",
"Name": "dosancto",
"Email": "johndoe@example.com"
},
"Chat": {
"Id": "chat#1",
"Name": "My First Chat",
"CreatedAt": "2023-02-28",
"UsersOnChat": {...}
}
}
O maior consumo neste caso está no tráfego do conteúdo, já que pode sofre do problema do overfetch(trazer mais dados do que o que será consumido), porém, caso algum servido necessite de mais contexto, é bem provável que nenhuma requisição extra seja necessária.
Quando a mensagem for preparada, muito provavelmente já vai ter esses dados resolvidos e prontos para uso.
Deve ter cuidado ao preparar esses Aggregates
Na maioria dos casos não é interessante reutilizar diretamente a Entidade principal para representar esses subsets de dados, pois frequentemente vão conter informações inúteis para um determinado domínio.
Por exemplo, um chat pode conter uma lista de Tags, mas para uma mensagem isto não faz diferença alguma. Portanto, crie classes menores para esses subsets e faça o decido mapeamento entre elas.
E sim, o código final será composto por diversas classes “repetidas” com bastante similares e lotados de mapeamentos, mas é um preço a se pagar visando uma aplicação modular.
Mais sobre
Um bom exemplo disso são os payload do OpenDelivery, são enormes aglomerados de dados relevantes para cada tipo de payload.
Isso faz com que cada um tenha as informações necessária para cada tipo de domínio.