Pular para o conteúdo

Repositórios

Para Eric Evans, o Repositório fornece a ilusão de uma coleção em memória de todos os objetos de um tipo de agregado. Encapsula o mecanismo de armazenamento e recuperação, mantendo o domínio ignorante de SQL, ORM ou qualquer detalhe de infraestrutura.

Ponto crucial de arquitetura: a interface do repositório é declarada no domínio, expressa na Linguagem Ubíqua (buscarPorCliente, salvar). A implementação vive na infraestrutura. Isso concretiza o DIP - Dependency Inversion Principle (ver SOLID): o domínio depende de abstração, e a infra depende do domínio.

Critério Repositório (DDD) DAO
Granularidade Um por raiz de agregado Frequentemente um por tabela
Abstração Coleção de agregados Operações CRUD sobre linhas
Linguagem Ubíqua, orientada ao domínio Técnica, orientada a dados
Retorno Agregados completos e consistentes Registros/DTOs

Mantém o domínio puro e testável: regras de negócio não conhecem persistência. Permite trocar Postgres por Mongo, ou mockar a persistência em testes, sem tocar no modelo. É o pilar que viabiliza a Arquitetura Hexagonal e a Clean Architecture.

  1. Um repositório por raiz de agregado. Não crie repositório para entidades internas.
  2. Interface no domínio, implementação na infra. O domínio nunca importa jakarta.persistence nem Spring Data.
  3. Trabalhe com agregados inteiros e consistentes, não com fragmentos.
  4. Consultas complexas de leitura podem escapar do repositório de escrita (ver CQRS); force menos o agregado no lado de leitura.

Interface no domínio (Port), entidade JPA separada do modelo de domínio, e implementação como Adapter.

package com.loja.pedido.domain;
import java.util.Optional;
public interface PedidoRepository {
void salvar(Pedido pedido);
Optional<Pedido> porId(PedidoId id);
void remover(PedidoId id);
}
package com.loja.pedido.infrastructure.persistence;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.util.UUID;
@Entity
@Table(name = "pedido")
public class PedidoJpaEntity {
@Id
private UUID id;
@Column(name = "cliente_id")
private UUID clienteId;
@Column(name = "status")
private String status;
@Column(name = "total")
private BigDecimal total;
protected PedidoJpaEntity() {
}
public PedidoJpaEntity(UUID id, UUID clienteId, String status, BigDecimal total) {
this.id = id;
this.clienteId = clienteId;
this.status = status;
this.total = total;
}
public UUID getId() {
return id;
}
public UUID getClienteId() {
return clienteId;
}
public String getStatus() {
return status;
}
public BigDecimal getTotal() {
return total;
}
}
package com.loja.pedido.infrastructure.persistence;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PedidoJpaRepository extends JpaRepository<PedidoJpaEntity, UUID> {
}
package com.loja.pedido.infrastructure.persistence;
import com.loja.pedido.domain.Pedido;
import com.loja.pedido.domain.PedidoId;
import com.loja.pedido.domain.PedidoRepository;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class PedidoRepositoryImpl implements PedidoRepository {
private final PedidoJpaRepository jpa;
private final PedidoMapper mapper;
public PedidoRepositoryImpl(PedidoJpaRepository jpa, PedidoMapper mapper) {
this.jpa = jpa;
this.mapper = mapper;
}
@Override
public void salvar(Pedido pedido) {
jpa.save(mapper.paraJpa(pedido));
}
@Override
public Optional<Pedido> porId(PedidoId id) {
return jpa.findById(id.valor()).map(mapper::paraDominio);
}
@Override
public void remover(PedidoId id) {
jpa.deleteById(id.valor());
}
}
graph LR
    subgraph Dominio["Domínio (núcleo)"]
        Svc["Use Case / Domain Service"]
        Port["interface PedidoRepository"]
    end
    subgraph Infra["Infraestrutura (adapter)"]
        Impl["PedidoRepositoryImpl"]
        Jpa["PedidoJpaRepository (Spring Data)"]
        Db[("Banco de Dados")]
    end
    Svc --> Port
    Impl -.implementa.-> Port
    Impl --> Jpa --> Db
  • Vazamento de infraestrutura no domínio: anotar o modelo de domínio com @Entity ou fazer o domínio importar Spring Data.
  • Repositório por tabela/entidade interna: deve haver um por raiz de agregado, não por ItemPedido.
  • Expor JpaRepository como se fosse o repositório de domínio: acopla o domínio ao Spring Data e ao paginado/CRUD genérico.
  • Retornar fragmentos inconsistentes: devolver metade de um agregado, violando invariantes ao salvar.

1. Onde fica a interface do repositório e onde fica a implementação?

Resposta

A interface no domínio (é um Port, expresso na linguagem ubíqua) e a implementação na infraestrutura (um Adapter com Spring Data). Isso concretiza o DIP - Dependency Inversion Principle.

2. Quantos repositórios deve haver por agregado?

Resposta

Um por raiz de agregado. Entidades internas (como ItemPedido) não têm repositório próprio; são acessadas via a raiz.

3. Como o Repositório difere de um DAO?

Resposta

O Repositório é uma coleção de agregados inteiros e consistentes, orientado à linguagem do domínio. O DAO costuma ser por tabela, orientado a CRUD e a dados, sem noção de agregado.

4. Por que separar PedidoJpaEntity do Pedido de domínio?

Resposta

Para evitar vazamento de infraestrutura no domínio: o modelo de domínio fica livre de anotações de persistência, e o mapeamento acontece no adapter, preservando a pureza e a testabilidade do núcleo.