Software Architecture Lab
Voltar para conceitos
BackendIntermediário15 min

Repository Pattern

Isole o acesso a dados atrás de uma interface clara. O Repository evita que lógica de banco vaze para o domínio da aplicação.

Resumo

O Repository Pattern é uma camada de abstração entre o domínio da aplicação e o mecanismo de persistência. Em vez de chamar o banco de dados diretamente dentro de serviços ou controllers, você define uma interface que representa a coleção — e a implementação concreta fica escondida atrás dela.

O resultado: o restante do código não sabe (e não precisa saber) se os dados vêm de um banco relacional, um banco NoSQL, uma API externa ou da memória.

O problema

Sem o Repository Pattern, a lógica de negócio e o acesso a dados ficam misturados:

// Dentro de um service
async function processarPedido(pedidoId: string) {
  const pedido = await db.query(
    "SELECT * FROM pedidos WHERE id = $1",
    [pedidoId]
  );
  // lógica de negócio misturada com SQL
  if (pedido.status === "pendente") {
    await db.query(
      "UPDATE pedidos SET status = $1 WHERE id = $2",
      ["aprovado", pedidoId]
    );
  }
}

Problemas concretos:

  • Testar a lógica exige um banco de dados real
  • Mudar de ORM ou banco significa reescrever tudo
  • O mesmo SQL aparece em vários lugares

Antes e depois

Acesso direto ao banco — lógica vazada
class PedidoService {
  async aprovar(id: string) {
    const rows = await db.query(
      "SELECT * FROM pedidos WHERE id = $1", [id]
    );
    const pedido = rows[0];
 
    if (pedido.status !== "pendente") return;
 
    await db.query(
      "UPDATE pedidos SET status = 'aprovado' WHERE id = $1", [id]
    );
  }
}
Com Repository — domínio isolado
interface PedidoRepository {
  findById(id: string): Promise<Pedido | null>;
  save(pedido: Pedido): Promise<void>;
}
 
class PedidoService {
  constructor(private repo: PedidoRepository) {}
 
  async aprovar(id: string) {
    const pedido = await this.repo.findById(id);
    if (!pedido || pedido.status !== "pendente") return;
 
    pedido.status = "aprovado";
    await this.repo.save(pedido);
  }
}

No segundo exemplo, PedidoService não tem nenhuma dependência de banco. É possível testá-lo com um repositório em memória — sem Docker, sem setup de banco.

Quando usar

Use o Repository quando:

  • A lógica de negócio precisa ser testada sem banco de dados
  • O mesmo tipo de dado é acessado em mais de um lugar do sistema
  • Há possibilidade de trocar o mecanismo de persistência (ex: migrar de PostgreSQL para MongoDB)
  • Você quer separar claramente o que é domínio do que é infraestrutura

Erros comuns

Repository que vaza SQL para a interface:

// Errado: a interface expõe detalhes de implementação
interface PedidoRepository {
  query(sql: string): Promise<any[]>;
}
 
// Correto: a interface fala a linguagem do domínio
interface PedidoRepository {
  findByStatus(status: StatusPedido): Promise<Pedido[]>;
}

Repository que virou um god object: Adicionar métodos como findAllWithJoinsAndFiltersAndPagination transforma o Repository em uma camada de relatório. Consultas complexas merecem queries dedicadas — não precisam passar pelo Repository.

Retornar entidades do banco diretamente: O Repository deve retornar objetos do domínio (Pedido), não registros brutos do banco (PedidoRow). A tradução entre os dois é responsabilidade da implementação do repositório.

Conceitos relacionados

  • Service Layer — usa o Repository para executar lógica de negócio sem tocar no banco diretamente
  • Injeção de Dependência — mecanismo pelo qual o Repository concreto é fornecido ao Service em tempo de execução
  • Separação de Responsabilidades — o princípio que justifica manter domínio e persistência separados

Conteúdos relacionados