DDD
- Agregados na prática
![]() |
Hoje vou apresentar o conceito de Agregados (Aggregates) e como usá-los na prática na plataforma .NET. |
O Domain-Driven Design (DDD) é uma metodologia poderosa para construir sistemas de software complexos que representam de perto o domínio do mundo real que atendem.
Um dos conceitos fundamentais em DDD são os Aggregates
ou Agregados, que desempenham um papel central na organização e gerenciamento do
modelo de domínio.
Neste artigo, vou apresentar os conceitos básicos sobre os Agregados seu significado.
O que é um Agregado
Um Agregado é
uma estrutura de modelagem que define um conjunto de entidades e objetos de
valor que são tratados como uma única unidade dentro do domínio de um
aplicativo. O principal objetivo de um Agregado é garantir a consistência e a
integridade dos dados no domínio, controlando o acesso e a modificação das
entidades e objetos de valor relacionados.
É
importante notar que, embora um Agregado possa
conter várias entidades e objetos de valor, ele tem uma raiz que atua como o
ponto de entrada para o Agregado e é responsável
por controlar o acesso aos elementos internos.
A raiz do Agregado é conhecida como Raiz Agregada
O que é uma Raiz
Agregada ?
Cada agregado possui um ponto de entrada designado, conhecido como raiz
agregada. A Raiz Agregada controla o acesso aos
objetos dentro do Agregado e impõe invariantes e regras de negócios dentro do
domínio. Este conceito fornece uma maneira de encapsular objetos de domínio
relacionados, garantindo consistência e integridade dos dados.
No DDD, determinar os limites dos agregados e suas raízes agregadas é uma
decisão de design crucial e tudo depende do caso de uso de negócios. No
entanto, os seguintes cenários podem ajudar a orientar a decisão de criar
agregados separados ou usar um agregado existente:
A função do Aggregate Root ou Raiz Agregada inclui:
Ciclo de vida de um Agregado
No Domain-Driven Design (DDD), um Agregado não é apenas uma estrutura estática de dados; ele possui um ciclo de vida bem definido, que abrange desde a sua criação até a sua persistência e, eventualmente, a sua remoção. A Raiz Agregada (Aggregate Root) é a guardiã desse ciclo, sendo o único ponto de entrada para todas as operações que afetam o estado do Agregado.
1. Criação do Agregado
O ciclo de vida de um Agregado começa com a sua criação. Isso sempre ocorre através da sua Raiz Agregada. Você nunca deve criar uma entidade interna de um Agregado de forma isolada e depois tentar "anexá-la" à Raiz Agregada. A criação deve respeitar os invariantes do Agregado desde o primeiro momento.
- Construtor da Raiz Agregada: O construtor da Raiz Agregada é o local ideal para inicializar o Agregado, garantir que todos os dados obrigatórios estejam presentes e que os invariantes iniciais sejam satisfeitos.
- Fábricas (Factories): Para lógicas de criação mais complexas (por exemplo, quando um Agregado depende de outros para ser criado), você pode usar um objeto Fábrica de Domínio. A Fábrica encapsula a complexidade da criação, garantindo que o Agregado seja instanciado em um estado válido.
Exemplo: Ao criar um
Pedido
(Order), você o faz através
do construtor da classe Order
, que
é a Raiz Agregada. Esse construtor pode receber informações essenciais, como o
ShippingAddress
e garantir que o
pedido tenha um OrderId
e OrderDate
válidos.
public class Order { public Guid OrderId { get; private set; } public DateTime OrderDate { get; private set; } public List<OrderItem> Items { get; private set; } public ShippingAddress ShippingAddress { get; private set; } // Construtor é o ponto de criação da Aggregate Root public Order(ShippingAddress shippingAddress) { OrderId = Guid.NewGuid(); OrderDate = DateTime.UtcNow; Items = new List<OrderItem>(); ShippingAddress = shippingAddress; // Aqui você pode adicionar validações iniciais (invariantes) if (shippingAddress == null) { throw new ArgumentNullException(nameof(shippingAddress), "Shipping address is required to create an order."); } } // ... outros métodos } |
2. Modificação do Agregado
Após a criação, o Agregado pode ser modificado. Todas as
alterações no estado interno do Agregado (sejam na Raiz Agregada, nas entidades
internas ou nos objetos de valor) devem ser orquestradas e validadas pela Raiz
Agregada. Isso garante que os invariantes do Agregado sejam mantidos em todas as
operações.
- Métodos Comportamentais: As
modificações não são feitas diretamente em propriedades públicas; em vez disso,
são feitas através de métodos bem definidos na Raiz Agregada que expressam a
intenção do domínio. Esses métodos encapsulam a lógica de negócio e as
validações.
- Eventos de Domínio: Durante uma
modificação, a Raiz Agregada pode optar por publicar Eventos de Domínio para
notificar outras partes do sistema sobre o que aconteceu. Isso é crucial para
comunicação entre Agregados e para desacoplamento.
Exemplo:
Adicionar um item a um Pedido é feito através do método AddItem na classe Order.
Esse método pode conter lógicas de validação (ex: quantidade mínima, estoque
disponível) antes de adicionar o item.
public class Order { // ... propriedades e construtor // Método para adicionar um item ao pedido, controlando a lógica de negócio public void AddItem(Guid productId, int quantity, decimal unitPrice) { if (quantity <= 0) { throw new ArgumentException("Quantity must be positive."); } // Exemplo de invariante: verificar se o preço total do pedido não excede um limite // decimal currentTotal = Items.Sum(item => item.Quantity * item.UnitPrice); // if (currentTotal + (quantity * unitPrice) > MAX_ORDER_VALUE) // { // throw new InvalidOperationException("Order total exceeds maximum allowed value."); // } Items.Add(new OrderItem { ProductId = productId, Quantity = quantity, UnitPrice = unitPrice }); // Opcionalmente, emitir um evento de domínio: // DomainEvents.Raise(new OrderItemAdded(OrderId, productId, quantity, unitPrice)); } public void ChangeShippingAddress(ShippingAddress newAddress) { if (newAddress == null) { throw new ArgumentNullException(nameof(newAddress)); } this.ShippingAddress = newAddress; // Opcionalmente, emitir um evento de domínio: // DomainEvents.Raise(new ShippingAddressChanged(OrderId, newAddress)); } } |
3. Persistência e Recuperação
Agregados são a unidade de persistência transacional. Isso significa que quando
você salva um Agregado, todas as suas partes internas (Raiz Agregada, entidades
internas e objetos de valor) são salvas juntas como uma única transação atômica.
Se qualquer parte falhar, toda a transação é revertida, garantindo a
consistência.
- Repositórios: A responsabilidade
de persistir e recuperar Agregados é dos Repositórios. Um Repositório é uma
coleção de Agregados do mesmo tipo, e ele sempre lida com o Agregado como uma
unidade completa.
- Atomicidade: A garantia de que
o Agregado é salvo ou falha como uma unidade é fundamental para a integridade
dos dados.
Exemplo: Um OrderRepository seria responsável
por salvar e buscar instâncias de Order.
|
4. Remoção do Agregado
A remoção
de um Agregado também deve ser feita através da sua Raiz Agregada e,
consequentemente, via Repositório. Assim como na criação e modificação, a
remoção é uma operação transacional que afeta todas as partes do Agregado como
uma unidade.
- Comportamento de Negócio: A
decisão de remover um Agregado geralmente é um comportamento de negócio, e não
apenas uma exclusão de dados. Por exemplo, um Pedido só pode ser "cancelado" se
estiver em um determinado status. Essa lógica deve ser encapsulada.
- Impacto Cascata: A remoção da Raiz Agregada implica na
remoção de todas as entidades e objetos de valor que pertencem a ele.
Compreender o ciclo de vida de um Agregado é crucial para modelar o domínio de forma eficaz, garantindo a integridade dos dados e um design robusto e maleável.
Quando incluir entidades
dentro do agregado existente?
Uma Raiz Agregada define um limite transacional
dentro do qual as mudanças devem ocorrer atomicamente. Em outras palavras,
qualquer modificação no estado de um Agregado, incluindo a própria Raiz Agregada
e suas entidades associadas, deve ser totalmente bem-sucedida ou falhar. Isso
garante que os dados no agregado permaneçam em um estado consistente.
As Invariantes são as regras que devem ser mantidas dentro do Agregado para
garantir a integridade dos dados. Esses invariantes representam regras ou
condições de negócios que devem ser sempre verdadeiras.
Por exemplo, se você estiver modelando um sistema de
comércio eletrônico, uma invariante pode ser que o preço total de todos os itens
do pedido não deva aumentar pelo valor máximo permitido no limite do pedido.
Portanto, o Pedido e o Item
do Pedido geralmente fazem parte do mesmo agregado
Como regra geral, toda vez que quisermos operar em uma Raiz Agregada, devemos
recuperar o objeto completo.
Como exemplo, suponha que estamos criando um sistema de comércio eletrônico e precisamos modelar a entidade "Pedido" com seus detalhes, como "Item de Pedido" e "Endereço de Entrega".
Vamos aplicar o conceito de Agregados e Rais Agregada.
// Entidade de Item de Pedido
public class OrderItem
{
public Guid ProductId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
// Objeto de Valor para Endereço de Entrega
public class ShippingAddress
{
public string? Street { get; set; }
public string? City { get; set; }
public string? PostalCode { get; set; }
}
// Agregado Root: Pedido
public class Order
{
public Guid OrderId { get; private set; }
public DateTime OrderDate { get; private set; }
public List<OrderItem> Items { get; private set; }
public ShippingAddress ShippingAddress { get; private set; }
public Order(ShippingAddress shippingAddress)
{
OrderId = Guid.NewGuid();
OrderDate = DateTime.UtcNow;
Items = new List<OrderItem>();
ShippingAddress = shippingAddress;
}
// Método para adicionar um item ao pedido
public void AddItem(Guid productId, int quantity, decimal unitPrice)
{
Items.Add(new OrderItem
{
ProductId = productId,
Quantity = quantity,
UnitPrice = unitPrice
});
}
}
|
Neste exemplo:
A Raiz Agregada Order controla o acesso aos elementos internos, garantindo a consistência e a integridade dos dados. Este é um exemplo simplificado, mas ilustra o conceito de Agregados e como eles ajudam a modelar o domínio de um sistema de forma coesa e consistente.
A seguir um exemplo bem básico usando o Agregado :
class Program
{
static void Main()
{
// Criação de um novo pedido com um endereço de entrega
var shippingAddress = new ShippingAddress
{
Street = "123 Main St",
City = "Exampleville",
PostalCode = "12345"
};
var order = new Order(shippingAddress);
// Adicionando itens ao pedido
order.AddItem(Guid.NewGuid(), 2, 25.00M);
order.AddItem(Guid.NewGuid(), 1, 10.00M);
// Agora, o pedido encapsula os itens de pedido e o endereço
// de entrega como um Agregado.
// Todas as operações no pedido são realizadas por meio da raiz
// do Agregado, que é a própria instância do pedido.
}
}
|
Para concluir , aqui estão os principais pontos relacionados ao conceito de Agregado:
É importante lembrar que os Agregados são definidos e vivem dentro de um Contexto Delimitado, que é um limite explícito de um modelo de domínio específico, garantindo que as regras de negócio de um Agregado sejam consistentes dentro desse contexto."
E estamos conversados...
"Ouve tu então nos céus, assento da tua habitação, e
perdoa, e age, e dá a cada um conforme a todos os seus caminhos, e segundo vires
o seu coração, porque só tu conheces o coração de todos os filhos dos homens."
1 Reis 8:39
Referências:
.NET MAUI - Lançamento da Release Candidate