0Pricing
PHP Academy · Aula

Agregados, repositórios e fábricas

Proteja invariantes com agregados e persista-os de forma organizada.

Agregados, repositórios e fábricas é uma aula grátis de PHP Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de PHP Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de PHP Academy inclui 4 aulas no total.

Protegendo invariantes

Depois de ter entidades e objetos de valor, você precisa de padrões para manter conjuntos deles consistentes e para persisti-los de forma organizada. O DDD responde com três padrões táticos: o agregado (um limite de consistência), o repositório (uma abstração de persistência semelhante a uma coleção) e a fábrica (para construções complexas). Esta lição mostra como eles se encaixam no PHP.

O que é um agregado

Um agregado é um conjunto de entidades e objetos de valor tratado como uma única unidade para alterações de dados. Uma entidade é a raiz do agregado — o único membro ao qual o código externo pode manter uma referência. Todas as modificações passam pela raiz, que impõe as invariantes do agregado. O agregado também é o limite transacional: ele é carregado e salvo atomicamente.

A raiz protege o todo

O código externo nunca acessa os membros internos diretamente. Para adicionar um item de linha, você chama um método na raiz, que valida e mantém a consistência (totais, limites). Isso mantém as invariantes em um só lugar.

<?php
final class LineItem {
    public function __construct(
        public readonly string $sku,
        public readonly int $qty,
        public readonly int $unitCents
    ) {}
    public function subtotal(): int { return $this->qty * $this->unitCents; }
}

final class Order { // Aggregate Root
    /** @var LineItem[] */
    private array $items = [];
    public function __construct(public readonly string $id) {}

    public function addItem(string $sku, int $qty, int $unitCents): void {
        if ($qty < 1) { throw new DomainException('qty must be >= 1'); }
        $this->items[] = new LineItem($sku, $qty, $unitCents);
    }
    public function total(): int {
        return array_sum(array_map(fn(LineItem $i) => $i->subtotal(), $this->items));
    }
}
$o = new Order('o1');
$o->addItem('A', 2, 500);
$o->addItem('B', 1, 300);
echo $o->total(), PHP_EOL; // 1300

Projetando agregados pequenos

Um erro frequente é tornar os agregados grandes demais (um Order que também possui todo o grafo de Customer). Regras práticas:

  • Mantenha os agregados pequenos; referencie outros agregados pelo identificador, em vez de manter o objeto.
  • Uma única transação deve modificar um agregado; coordene operações entre agregados com eventos de domínio.
  • As invariantes que precisam ser sempre válidas definem o limite.

Referenciar pelo identificador

O pedido armazena um objeto de valor customerId, não uma entidade Customer. Isso mantém o limite de consistência restrito e evita carregar grafos de objetos enormes. A consistência entre agregados torna-se eventual, sendo tratada por eventos em vez de uma única transação gigantesca.

<?php
final class CustomerId {
    public function __construct(public readonly string $value) {}
}
final class Order {
    public function __construct(
        public readonly string $id,
        public readonly CustomerId $customerId // reference, not object
    ) {}
}
$order = new Order('o1', new CustomerId('cus_99'));
echo $order->customerId->value, PHP_EOL;

O contrato do repositório

Um repositório oferece a ilusão de uma coleção em memória de raízes de agregados. O domínio depende apenas da interface; a implementação (Doctrine, PDO, em memória) fica na camada de infraestrutura. Os repositórios lidam com agregados completos, nunca com linhas parciais.

<?php
interface OrderRepository {
    public function ofId(string $id): ?Order;
    public function save(Order $order): void;
    public function nextIdentity(): string;
}

Uma implementação em memória

Um repositório em memória é inestimável para testes unitários rápidos e sem banco de dados. Como o domínio depende da interface, você pode trocar as implementações livremente (Inversão de Dependência em ação).

<?php
interface OrderRepository {
    public function ofId(string $id): ?object;
    public function save(object $order): void;
    public function nextIdentity(): string;
}
final class Order { public function __construct(public readonly string $id) {} }

final class InMemoryOrderRepository implements OrderRepository {
    private array $store = [];
    public function ofId(string $id): ?object { return $this->store[$id] ?? null; }
    public function save(object $order): void { $this->store[$order->id] = $order; }
    public function nextIdentity(): string { return 'o_' . bin2hex(random_bytes(4)); }
}
$repo = new InMemoryOrderRepository();
$repo->save(new Order('o1'));
var_dump($repo->ofId('o1') !== null);

O repositório não é um DAO

Um repositório não é um componente genérico para criar, ler, atualizar e excluir dados. Ele expõe consultas com significado para o domínio (findOverdueOrders()) e reconstitui agregados completos com suas invariantes intactas. Ele oculta deliberadamente os detalhes de SQL e do mapeador objeto-relacional para que o domínio não tenha conhecimento da persistência. Evite expor construtores de consulta ou métodos genéricos como save($anyEntity) ao domínio.

Fábricas para criações complexas

Quando construir um agregado envolve lógica real — gerar a identidade, montar objetos de valor e impor invariantes no momento da criação — transfira essa responsabilidade para uma fábrica (uma classe dedicada ou um construtor nomeado estático). Isso mantém o construtor da entidade honesto e centraliza as regras para uma criação válida.

<?php
final class Order {
    private function __construct(
        public readonly string $id,
        public readonly string $customerId
    ) {}
    public static function place(string $customerId): self {
        if ($customerId === '') { throw new DomainException('customer required'); }
        return new self('o_' . bin2hex(random_bytes(4)), $customerId);
    }
}
$order = Order::place('cus_1');
echo $order->id, PHP_EOL;

Como eles colaboram

O fluxo típico em um serviço de aplicação:

  • Uma fábrica (ou um construtor nomeado) cria um agregado válido.
  • Os métodos da raiz do agregado impõem as invariantes durante o uso.
  • Um repositório persiste e depois reconstitui o agregado completo.

O serviço de aplicação coordena essas operações dentro de uma transação por agregado, dependendo apenas de interfaces.

Garantindo uma invariante de todo o agregado

O verdadeiro valor da raiz está em impor invariantes que abrangem os membros. Aqui, o pedido rejeita um item de linha se ele fizer o total ultrapassar um limite de crédito — uma regra que nenhum LineItem poderia impor sozinho. Como todas as alterações passam pela raiz, a regra nunca pode ser contornada.

<?php
final class Order {
    private array $items = [];
    public function __construct(
        public readonly string $id,
        private int $creditLimitCents
    ) {}
    public function addItem(int $cents): void {
        if ($this->total() + $cents > $this->creditLimitCents) {
            throw new DomainException('Exceeds credit limit');
        }
        $this->items[] = $cents;
    }
    public function total(): int { return array_sum($this->items); }
}
$o = new Order('o1', 1000);
$o->addItem(600);
try { $o->addItem(600); } catch (DomainException $e) { echo $e->getMessage(), PHP_EOL; }
echo $o->total(), PHP_EOL; // 600

Verificação rápida

Projeto de agregado.

Recapitulação

Você aprendeu a proteger invariantes e persistir de forma organizada. Os agregados formam um limite de consistência e transacional, são modificados apenas por meio de sua raiz e mantidos pequenos ao referenciar outros agregados pelo identificador. Os repositórios apresentam raízes de agregados como uma coleção por trás de uma interface de domínio, ocultando o mapeador objeto-relacional e o SQL e permitindo dublês de teste em memória. As fábricas centralizam criações complexas que impõem invariantes. Juntos, eles mantêm o modelo de domínio consistente e sem conhecimento da persistência.

Perguntas Frequentes

A aula “Agregados, repositórios e fábricas” é grátis?

Sim — o texto completo de “Agregados, repositórios e fábricas” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de PHP Academy, atualize para CoddyKit PRO. O curso de PHP Academy inclui 4 aulas no total.

O que vou aprender em “Agregados, repositórios e fábricas”?

Proteja invariantes com agregados e persista-os de forma organizada. Você pratica PHP Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar PHP Academy?

Nenhuma experiência prévia é necessária. PHP Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.

Quanto tempo leva a aula “Agregados, repositórios e fábricas”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de PHP Academy?

Sim. Cada aula de PHP Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Blocos básicos de DDD: entidades e objetos de valor
  2. Agregados, repositórios e fábricas
  3. Eventos e serviços de domínio
  4. Contextos delimitados e mapeamento de contextos
← Voltar para PHP Academy