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
- Blocos básicos de DDD: entidades e objetos de valor
- Agregados, repositórios e fábricas
- Eventos e serviços de domínio
- Contextos delimitados e mapeamento de contextos