Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

05 - Fábrica (Factory)

#criacao-de-objetos#ddd#factory#invariantes
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Fábrica (Factory)

A fábrica é o mecanismo responsável por criar objetos complexos de domínio, garantindo que toda
instância nasça válida e com suas invariantes satisfeitas. O padrão existe porque a criação de
objetos ricos não é trivial: um agregado pode exigir vários objetos interligados, regras de
negócio sobre o estado inicial e a escolha entre subtipos, e espalhar essa responsabilidade por
quem consome o objeto degrada o modelo. No vocabulário de DDD, fábrica é um papel que pode ser
cumprido por um método estático na própria classe, por um método na raiz de um agregado vizinho ou
por uma classe dedicada, e a escolha depende de onde vive o conhecimento necessário para construir
o objeto. Em produção, concentração da criação significa um único lugar onde o estado inicial é
decidido, o que reduz a classe de bugs em que objetos inválidos nascem e falham longe da origem.

O problema que resolve

Quando o construtor público exige dez parâmetros, o chamador costuma improvisar: monta o objeto e
depois ajusta campos, ou cria construtores alternativos em outros pontos do código. Cada improvviso
é uma oportunidade para nascer um estado inválido, como pedido sem itens, conta com saldo negativo
ou assinatura ativa sem forma de pagamento. O problema piora com agregados, porque criar a raiz
pode exigir criar entidades internas, definir valores iniciais de vários objetos e registrar
relações com outros agregados. Também existe o problema da escolha de subtipo: quem precisa de um
método de pagamento não deveria conhecer todas as classes concretas de cartão, boleto e pix, mas
apenas pedir "crie o método a partir desta preferência". A fábrica responde aos três casos com um
ponto único de criação que conhece as regras de nascimento do objeto.

Preservação de invariantes na criação

O contrato essencial é que tudo o que sai da fábrica é válido. Isso significa que as regras sobre
o estado inicial vivem dentro da fábrica, e não no chamador: um pedido só nasce com ao menos um
item, uma transferência só nasce entre contas distintas, uma conta só nasce com saldo inicial não
negativo. Construtores públicos devem ser simples ou privados, e a construção completa passa pela
fábrica, que pode validar, calcular valores derivados e montar as partes internas. Há uma
distinção importante entre criação e reconstituição: criar é nascer um novo objeto com regras de
negócio aplicadas, e reconstituir é trazer de volta um estado que já existia, geralmente a partir
do banco, sem reexecutar regras de nascimento. Misturar os dois caminhos é erro comum, pois
reconstituir um pedido histórico aplicando regra de criação corrente altera o passado registrado.

Factory method e fábrica de agregado

O factory method é a forma mais enxuta: um método estático dentro da própria classe, com nome de
negócio, que decide como construir a si mesma. Pedido.abrir, Dinheiro.of e Cpf.de são exemplos,
e cobrem a maioria dos casos, porque a classe conhece suas próprias invariantes. A fábrica
dedicada, uma classe separada, entra quando a criação exige conhecimento que não pertence à classe
criada: montar um agregado inteiro a partir de um template, carregar dados de outro contexto ou
coordenar a criação de vários objetos interligados. Um terceiro caso é o método de fábrica em
agregado vizinho, quando a regra pertence ao agregado existente: é o pedido que cria seu próprio
item, e não uma classe externa que constrói o item e injeta na lista interna. A escolha segue a
linguagem ubíqua: o negócio diz "abrir conta" ou "emitir pedido", e o nome do método de fábrica
reproduz essa frase.

Exemplo prático

O exemplo em Java mostra as três formas aplicadas ao mesmo domínio: factory method estático,
criação de entidade interna pela raiz e fábrica dedicada para montar um agregado a partir de um
carrinho.

public final class Pedido {
    private final PedidoId id;
    private final ClienteId clienteId;
    private final List<ItemDoPedido> itens = new ArrayList<>();

    private Pedido(PedidoId id, ClienteId clienteId) {   // construtor privado: ninguém monta a mão
        this.id = id;
        this.clienteId = clienteId;
    }

    // factory method: a própria classe conhece a regra de nascimento
    public static Pedido abrir(PedidoId id, ClienteId clienteId, PrimeiroItem primeiro) {
        if (primeiro == null) throw new PedidoExigeUmItem(id);
        Pedido pedido = new Pedido(id, clienteId);
        pedido.itens.add(primeiro.paraItem());           // nasce com item, invariante garantida
        return pedido;
    }

    // a raiz cria a entidade interna: a regra do item pertence ao pedido
    public void adicionarItem(ProdutoId produto, String nome, Dinheiro preco, int quantidade) {
        if (quantidade <= 0) throw new QuantidadeInvalida(produto, quantidade);
        if (itens.stream().anyMatch(i -> i.mesmoProduto(produto))) {
            throw new ProdutoJaAdicionado(id, produto);
        }
        itens.add(new ItemDoPedido(produto, nome, preco, quantidade));
    }
}

// fábrica dedicada: conhece dados de outro contexto e monta o agregado completo
public final class FabricaDePedidos {
    private final ServicoDePrecificacao precificacao;    // dependência do contexto de preço

    public FabricaDePedidos(ServicoDePrecificacao precificacao) {
        this.precificacao = precificacao;
    }

    public Pedido aPartirDoCarrinho(PedidoId novoId, Carrinho carrinho) {
        PrimeiroItem primeiro = null;
        for (ItemDeCarrinho item : carrinho.itens()) {
            PrecoCobrado preco = precificacao.precoPara(carrinho.clienteId(), item.produtoId());
            if (primeiro == null) {
                primeiro = new PrimeiroItem(item.produtoId(), item.nome(), preco.valor(), item.quantidade());
            } else {
                // itens seguintes entram pela raiz, respeitando as regras do agregado
            }
        }
        Pedido pedido = Pedido.abrir(novoId, carrinho.clienteId(), primeiro);
        for (ItemDeCarrinho item : carrinho.itensSemOPrimeiro()) {
            PrecoCobrado preco = precificacao.precoPara(carrinho.clienteId(), item.produtoId());
            pedido.adicionarItem(item.produtoId(), item.nome(), preco.valor(), item.quantidade());
        }
        return pedido;
    }
}

A fábrica dedicada absorve a orquestração da criação, e o caso de uso fica com uma linha:
fabrica.aPartirDoCarrinho(id, carrinho).

Factory, builder e construtor

Critério Construtor Factory method Fábrica dedicada Builder
Foco Instanciar com parâmetros Nascer válido com nome de negócio Montar agregados complexos Construção passo a passo opcional
Quem conhece as regras A própria classe A própria classe A classe fábrica O chamador, passo a passo
Escolha de subtipo Ruim, exige um construtor por tipo Boa com métodos nomeados Boa, com um método por caso Fraca, depende de validação final
Risco de estado inválido Alto quando há muitos parâmetros Baixo Baixo Alto se o build não validar
Uso típico Objetos de valor simples Entidades e objetos de valor Agregados vindos de outros contextos Montagem configurável e testes

O builder é útil na infraestrutura e em testes, mas no domínio costuma adiar a validação para o
final, o que enfraquece a garantia de nascimento válido. Prefira factory method por padrão e
fábrica dedicada quando a complexidade da montagem justifica.

Erros comuns e boas práticas

  • Deixar construtores públicos ao lado da fábrica. Cada construtor acessível é um caminho que
    dribla as regras de nascimento, e o padrão perde a garantia principal.
  • Nomear o método de fábrica como create, of ou build sem palavra do negócio. O nome é oportunidade
    de linguagem ubíqua: emitir, abrir, contratar e reservar contam a história certa.
  • Colocar a fábrica no módulo de infraestrutura. A criação é decisão de domínio, e a fábrica vive
    no mesmo módulo que as classes criadas.
  • Reexecutar regras de criação na reconstituição de objetos persistidos. A reconstituição recupera
    estado registrado, sem aplicar regra corrente sobre o passado.
  • Criar uma fábrica para cada classe como cerimônia. Quando o construtor simples já garante a
    validade, o construtor é a fábrica, e camadas extras só adicionam indireção.
  • Retornar tipos concretos quando há subtipos relevantes. A fábrica é o lugar natural de devolver
    a interface do domínio e esconder a escolha da implementação.