DDD
- Apresentando Aggregate e Aggregate Root
![]() |
Neste artigo vou apresentar o conceito de Aggregate e Aggregate Root no contexto do Domain Driven Design. |
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.
E o que é DDD ?
O Domain Driven Design ou DDD é uma metodologia
poderosa para construir sistemas de software complexos que representam de perto
o domínio do mundo real que atendem.
Assim DDD não é
framework, não é tecnologia, não é padrão de projeto não é linguagem
O DDD (Domain Driven Design) é uma abordagem ao design de software que se baseia
no conceito de domínio.
E o que é Domínio ?
O dominio refere-se à esfera de conhecimento,
atividade ou interesse em que se concentra um sistema. É a área de negócio para
a qual o software está sendo desenvolvido. Se o seu negócio é vender
cachorro quente o seu domínio vai envolver todo o processo envolvido nesta venda
e pode ter mais de um domínio como lanches, produtos, clientes, vendas etc.
Assim, o DDD visa facilitar a criação de aplicativos complexos, conectando as
partes relacionadas do software em um modelo focado no negócio que esta em
constante evolução.
Ao usarmos a
abordagem do DDD na criação de um projeto de software teremos que representar os
objetos do mundo real usando uma abstração que, no contexto do DDD são definidos
usando Entities e Value Objects que são expressas
como classes ou records.
Considerando um sistema de gestão de vendas onde podemos ter objetos
representados por classes como Order (Pedido), OrderItem (PedidoItem) e
Customer (Cliente) :
1- Order
|
public class Order { public int OrderId { get; set; } public List<OrderItem>? Items { get; set; }
public decimal CalculateTotal() { decimal total = 0.0m; foreach (var item in Items) { total += item.CalculateTotal(); } return total; } } |
2- OrderItem
|
public class OrderItem { public int ProductId { get; set; } public int Quantity { get; set; } public decimal CalculateTotal() { return 1.0m; // } } |
3- Customer
|
public class Customer { public int CustomerId { get; set; } public List<Order>? Orders { get; set; } public void AddOrder(Order order) { Orders.Add(order); } public decimal CalculateTotalOrders() { decimal total = 0.0m; foreach (var order in Orders) { total += order.CalculateTotal(); } return total; } } |
Ao considerarmos o
Aggregate no contexto do DDD temos que, no Aggregate
existe o conceito de "Aggregate Root", que é a
principal entidade que agrupa outras entidades relacionadas e todas as
interações e modificações nas entidades agrupadas devem ser realizadas através
da Aggregate Root.
Todas as operações dentro de um Aggregate devem ser tratadas como uma única transação consistente, isso significa que, se alguma operação falhar dentro do Aggregate, todas as mudanças devem ser revertidas. Além disso um Aggregate deve encapsular o comportamento e os estados das entidades relacionadas, e fora do Aggregate, outras partes do sistema só devem interagir com a Aggregate Root.
E para que serve o Aggregate ?
O padrão Aggregate ajuda a manter a consistência dos dados, garantindo que as operações dentro de um Aggregate sejam tratadas atomicamente.
O encapsulamento proporcionado pelo padrão ajuda a ocultar a complexidade interna das entidades relacionadas, expondo apenas a Aggregate Root sendo especialmente útil no contexto do Domain-Driven Design (DDD) para modelar relações complexas entre entidades de domínio.
Desta forma através do Aggregate Root, é possível controlar o acesso e as operações permitidas nas entidades agrupadas.
E como podemos definir um Aggregate e um Aggreate Root ?
Devemos considerar a Consistência Transacional que considera um Aggregate como uma transação atômica. Todas as mudanças nas entidades dentro de um Aggregate devem ser tratadas como uma única operação de transação. Isso ajuda a garantir a consistência dos dados.
Em outras palavras, qualquer modificação no estado de um Agregate, incluindo a própria Raiz Agregada (Aggregate Root) e suas entidades associadas, deve ser totalmente bem-sucedida ou falhar. Isso garante que os dados no Aggregate permaneçam em um estado consistente.
Outro conceito importante é o conceito de Invariantes, que são as regras que devem ser mantidas dentro do Agregate 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 Order ou Pedido e o OrderItem ou Item do Pedido geralmente fazem parte do mesmo agregado.
Assim, as entidades dentro de um Aggregate devem ter uma forte coesão conceitual e fazer sentido juntas no contexto do domínio. Se faz sentido agrupar certas entidades para garantir uma visão consistente, elas podem ser parte do mesmo Aggregate.
As fronteiras de consistência devem ser claramente definidas, e, isso significa que as regras de consistência devem ser aplicadas dentro do Aggregate e, idealmente, o Aggregate não deve depender de operações em outros Aggregates para manter sua consistência.
Obs: Quando entidades dentro do mesmo Agregado têm ciclos de vida diferentes, pode ser um sinal de que deveriam pertencer a um Agregado separado.
Com base nesses critérios podemos definir que para o sistema de gestão de vendas temos dois Aggregates :

Chegamos a esta definição aplicando os critérios acima às classes Order, OrderItem e Customer conforme descritos a seguir:
Com base nisso podemos criar o Controller CustomersController :
[Route("api/[controller]")]
[ApiController]
public class CustomersController : ControllerBase
{
private readonly ICustomerService _customerService;
public CustomersController(ICustomerService customerService)
{
_customerService = customerService;
}
[HttpPost]
public IActionResult CreateCustomer([FromBody] Customer customer)
{
var createdCustomer = _customerService.CreateCustomer(customer);
return Ok(createdCustomer);
}
[HttpGet("{customerId}")]
public IActionResult GetCustomer(int customerId)
{
var customer = _customerService.GetCustomer(customerId);
if (customer == null)
return NotFound();
return Ok(customer);
}
[HttpPost("{customerId}/orders")]
public IActionResult AddOrder(int customerId, [FromBody] Order order)
{
var addedOrder = _customerService.AddOrder(customerId, order);
return Ok(addedOrder);
}
}
|
E o serviço ICustomerService e sua implementação :
1- ICustomerService
public interface ICustomerService
{
Customer CreateCustomer(Customer customer);
Customer GetCustomer(int customerId);
Order AddOrder(int customerId, Order order);
}
|
2- CustomerService
|
public class
CustomerService : ICustomerService { private readonly List<Customer> _customers = new List<Customer>();
public Customer
CreateCustomer(Customer customer)
public Customer GetCustomer(int customerId)
public Order AddOrder(int customerId, Order order)
if (customer != null)
return null; // Cliente não encontrado |
Neste exemplo, adicionamos um método AddOrder no serviço para permitir a adição de um novo pedido para um cliente específico.
Na prática, você deve ajustar esses métodos de acordo com as necessidades específicas do seu domínio e da sua aplicação. Além disso, você pode considerar a utilização de um repositório de dados ou outros mecanismos de persistência, dependendo dos requisitos do seu projeto.
E estamos conversados...
"Porque a minha mão fez todas estas coisas, e assim
todas elas foram feitas, diz o Senhor; mas para esse olharei, para o pobre e
abatido de espírito, e que treme da minha palavra."
Isaías 66:2
Referências:
C# - Tasks x Threads. Qual a diferença
DateTime - Macoratti.net
Null o que é isso ? - Macoratti.net
Formatação de data e hora para uma cultura ...
C# - Calculando a diferença entre duas datas
NET - Padrão de Projeto - Null Object Pattern
C# - Fundamentos : Definindo DateTime como Null ...
C# - Os tipos Nullable (Tipos Anuláveis)