Executando verificação de segurança...
2

DDD - Resignificando Conceitos: Agregados

Dando continuidade ao assunto que viemos tratando nos últimos dias, este é a terceira publicação da série onde falamos sobre Modelagem Tática e Padrões aplicados ao DDD - Domain-Driven Design. Aqui vamos falar um pouco sobre Agregados e como aplicá-los na modelagem de seus projetos. Se perdeu algum conteúdo, confira aqui:

  1. DDD - Resignificando Conceitos: Entidades
  2. DDD - Resignificando Conceitos: Value Objects

Todos os objetos possuem um ciclo de vida. Um objeto nasce, provavelmente passa por várias mudanças e estados e finalmente morre, sendo arquivado ou excluído. Alguns são mais simples e outros possuem vidas mais longas, possuem interdependências mais complexas com outros objetos e passam por mudanças de estado às quais possuem regras de negócios essenciais que não podem ser violadas em nenhum momento do ciclo de vida do objeto de domínio, garantindo a integridade dos estados do domínio.

O ciclo de vida de um objeto de domínio

O controle do ciclo de vida desses objetos implica em dois desafios:

  • Manter a integridade dos estados durante todo o ciclo de vida;
  • Impedir que o modelo se deixe levar pela complexidade do gerenciamento do ciclo de vida.

Podemos tratar essas questões aplicando 3 padrões, que basicamente são Agregados, que manterão a integridade em todas as fases do ciclo de vida, mantendo a estrutura interna dos objetos encapsulada; Fábricas, que são responsáveis pela criação e reconstrução de objetos complexos no começo do ciclo de vida; e por fim, Repositórios, que tratam do meio e fim do ciclo de vida, fornecendo meios para buscar e recuperar objetos persistentes, encapsulando toda a infraestrutura envolvida. Neste artigo, vamos focar apenas nos agregados.

Agregado (Aggregate)

Um agregado é um conjunto de objetos associados que tratamos como uma unidade para propósito de mudança de dados, ou seja, um grupo de objetos de domínio que se comportam como uma entidade única que encapsula e organiza informações relacionadas, facilitando sua manipulação e compreensão em um sistema.

Cada agregado possui uma entidade raiz, que é o único membro de um agregado, o qual os objetos externos tem permissão de fazer referêcias e um limite define o que está dentro de um agregado. Os objetos dentro de um limite podem fazer referêcias livremente entre si

Para por em prática, vamos evoluir nosso sitema de gerenciamento de clientes para um sitema de pedidos de compras. Vamos adicionar ao código, mais 3 entidades: Order, que representa as ordens de compra; OrderItem, que representa um ou mais itens que compõe aquela ordem de compra e Product, que é o produto contido em um item, em uma ou mais quantidades:

  • Classe Order
from typing import List
from order_item import OrderItem

class Order:
    def __init__(self, order_id: str, customer_id: str, items: List[OrderItem]):
        self._id = order_id
        self._customer_id = customer_id
        self._items = items
        self._total = self.total()
        self.validate()

    @property
    def id(self) -> str:
        return self._id

    @property
    def customer_id(self) -> str:
        return self._customer_id

    @property
    def items(self) -> List[OrderItem]:
        return self._items

    def validate(self) -> None:
        if len(self._id) == 0:
            raise ValueError("Id is required")
        if len(self._customer_id) == 0:
            raise ValueError("CustomerId is required")
        if len(self._items) == 0:
            raise ValueError("Items are required")
        if any(item.quantity <= 0 for item in self._items):
            raise ValueError("Quantity must be greater than 0")

    def total(self) -> float:
        return sum(item.total() for item in self._items)

  • Classe OrderItem
class OrderItem:
    def __init__(self, item_id: str, name: str, price: float, product_id: str, quantity: int):
        self._id = item_id
        self._name = name
        self._price = price
        self._product_id = product_id
        self._quantity = quantity
        self._total = self.total()

    @property
    def id(self) -> str:
        return self._id

    @property
    def name(self) -> str:
        return self._name

    @property
    def product_id(self) -> str:
        return self._product_id

    @property
    def quantity(self) -> int:
        return self._quantity

    @property
    def price(self) -> float:
        return self._price

    def total(self) -> float:
        return self._price * self._quantity

  • Classe Product
class Product:
    def __init__(self, product_id: str, name: str, price: float):
        self._id = product_id
        self._name = name
        self._price = price
        self.validate()

    @property
    def id(self) -> str:
        return self._id

    @property
    def name(self) -> str:
        return self._name

    @property
    def price(self) -> float:
        return self._price

    def change_name(self, name: str) -> None:
        self._name = name
        self.validate()

    def change_price(self, price: float) -> None:
        self._price = price
        self.validate()

    def validate(self) -> None:
        if len(self._id) == 0:
            raise ValueError("Id is required")
        if len(self._name) == 0:
            raise ValueError("Name is required")
        if self._price < 0:
            raise ValueError("Price must be greater than zero")

Seguindo os preceitos de entidades expressivas que falamos no primeiro post, as entidades acima possuem seus métodos para obtenção de dados e validações, de modo que expressam os comportamentos e necessidades do negócio.
Recaptulando, até o momento temos 4 entidades, sendo elas Customer, Order, OrderItems e Product e 1 objeto de valor Address.

Observe o diagrama a seguir:

Relação de Entidades e Agregados

Na imagem acima, podemos ver uma divisão clara entre os objetos de domínio em 3 agregados. Essa divisão só começa a fazer sentidos quando colocamos em prática a definição de agregados, onde podemos ver que cada agregado se comporta como uma única entidade, onde a existência de uma entidade só faz sentido se existir as demais entidades dentro do limite do agregado. Vejamos:

  • Dentro de nosso contexto, um endereço só pode existir se for associado a um cliente e, por se tratar de compras online, não faz sentido existir um cliente sem um endereço, afinal, como ele receberia suas compras?
  • Se formos relacionar com uma ordem de compra, um cliente pode não ter nenhuma ordem de compra em dado momento, mas não existe nenhuma ordem de compra que não tenha sido realizada por um cliente. E pensando em itens de uma ordem, eles só podem existir se for associado a uma ordem de compra e é impossível existir uma ordem de compra que contenha 0 itens.
  • Por fim, um item deve sempre conter um produto associado, mas para um produto existir, ele não precisa estar em um item em uma ordem de compra de um cliente.

Apartir disso, começamos a ver claramente as delimitações que distinguem comportamentos e dependências entre as entidades. A entidade Customer é fortemente dependente de Address, a entidade Order é fortemente dependente da entidade OrderItems e possui relações com as entidades Customer e Product, e a entidade Product pode viver em um contexto totalmente apartado das demais entidades.

Nomeando Agreagados com Entidades Raiz

Para um agregado, damos o mesmo nome de sua entidade raiz. Como dito anteriormente, entidade raiz é aquela em que os objetos externos podem fazer referências.

  • Uma ordem de serviço não precisa carregar a entidade Customer inteira dentro dela, ela se relaciona com um cliente apenas pelo customerId do cliente, dado que um cliente pode existir sem ter uma ordem de compra, essa relação é dita como fraca. Sabendo então que as demais entidades podem fazer referência apenas à entidade Customer, este agregado deve se chamar Customer Aggregate.

  • Já devido a dependência entre uma ordem e um item de compra, o relacionamento entre elas é caracterizado como forte. Por isso, uma ordem de compra sempre vai trazer toda sua listagem de itens dentro dela. Neste agregado temos duas entidades que podem fazer referência a agregados externos, isso porque, objetos dentro do agregado podem fazer referências a outras raizes de agregados. Por hora podemos chama-lo de Order Aggregate e mais para frente, iremos tratar esse caso com mais detalhes.

  • Para produto, sua existência é independente da existencia de um item de compra, então a relação entre eles é fraca, sendo necessário apenas a referência do productId no item de compra. Sendo assim, devemos nomear este como Product Aggregate.

É importante ressaltar três pontos:

  • As invariantes (que são as regras de consistência que devem ser mantidas sempre que os dados sofrem alterações), envolvem apenas relações entre membros do agregado. Os agregados demarcam o escopo do qual as invariantes devem ser matindas cada estágio do ciclo de vida
  • Quando é feita uma alteração em qualquer objeto dentro do limite do agregado, todas as invariantes do agregado devem ser satisfeitas.
  • Uma operação de exclusão deve sempre remover tudo que estiver dentro do limite do agregado de uma vez só. Como não há referência externa a nada, exceto à raiz, exclua a raiz e tudo mais será removido.

Para finalizar, guarde esse resumo de ouro para você nunca mais errar na hora de definir seus agregados:

  1. Agrupe as entidades e objetos de valor em agregados com limites definidos em torno de cada um.
  2. Escolha uma entidade para ser a raiz de cada agregado.
  3. Controle todo o acesso aos objetos dentro do limite do agregado através da raiz.
  4. Como a raiz controla o acesso, ela não pode ser surpreendida com alterações na parte interna.

Seguindo esse arranjo, torna mais prática a execução de todas as invariantes para objetos no agregado e para o agregado como um todo em qualquer alteração de estado.

Nos próximos posts os padrões a serem abordados serão as Fábricas e Repositórios, que operam sobre os agregados, encapsulando a complexidade de transações específicas do ciclo de vida dos objetos de domínio.


Este artigo é resultado de estudos pessoais e foi profundamente inspirado pelo conteúdo apresentado no curso FullCycle 3.0. Agradeço à equipe da FullCycle pela excelente qualidade do material educacional fornecido. Todos os créditos e reconhecimentos vão para a FullCycle pela sua contribuição significativa para o meu aprendizado.

Linkedin: @dennis-policiano

Carregando publicação patrocinada...