Pular para o conteúdo principal

Repository

Descrição​

O Repository fica entre o domínio e a camada de mapeamento de dados e apresenta os objetos persistidos por meio de uma interface semelhante à de uma coleção em memória. O cliente adiciona, remove e busca objetos como faria em uma lista, sem saber como ou onde eles são armazenados.

Internamente, o Repository delega o trabalho a um Data Mapper e a Query Objects. Isso concentra a lógica de consulta em um só lugar e faz a dependência apontar do domínio para a camada de dados, não o contrário.

No DDD (Domain-Driven Design), Evans define o Repository como o mecanismo que dá a ilusão de uma coleção em memória de todos os objetos de um certo tipo. Há um Repository por Aggregate root: os objetos internos do agregado são acessados pela raiz, e o cliente não deve consultá-los diretamente. A interface é expressa na linguagem do domínio, e a criação de novos objetos fica com a Factory, enquanto o Repository cuida de armazenar e recuperar objetos existentes.

Problemas que resolve​

  • Código de domínio contaminado por detalhes de consulta e persistência.
  • Lógica de consulta duplicada em vários pontos da aplicação.
  • Dificuldade de testar o domínio sem banco de dados.
  • Domínio acoplado à tecnologia de acesso a dados.

Quando usar​

  • Quando o modelo de domínio é complexo e há muitas classes e consultas.
  • Quando se quer um limite claro entre o domínio e a infraestrutura de persistência.
  • Quando é desejável trocar o armazenamento por uma versão em memória nos testes.

Quando evitar​

  • Em sistemas simples ou CRUD, onde a camada extra não compensa.
  • Quando o ORM já oferece a abstração e o Repository só repetiria seus métodos.
  • Quando o Repository vira um depósito genérico de métodos de consulta, difícil de manter.

Trade-offs​

GanhoCusto / Risco
Domínio independente da persistênciaMais uma camada para manter
Lógica de consulta centralizadaPode duplicar a abstração do ORM
Fácil substituição por dublê em testesRisco de crescer com métodos de consulta específicos demais
Interface orientada a objetos e à coleçãoAbstração pode esconder custos (N+1, carga excessiva)
  • Acoplamento: o domínio depende apenas da interface do Repository.
  • Testabilidade: uma implementação em memória permite testes rápidos do domínio.
  • Consistência: a gravação em si costuma ser coordenada por um Unit of Work.

Diagrama​

O DomainService usa apenas a interface OrderRepository. A implementação delega ao OrderMapper e devolve objetos Order.

Exemplo​

Um serviço de pedidos precisa buscar e salvar pedidos sem conhecer o banco. O Spring Data gera a implementação a partir da interface.

public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerIdAndStatus(Long customerId, Status status);
}

@Service
public class OrderService {
private final OrderRepository orders;

public OrderService(OrderRepository orders) {
this.orders = orders;
}

public void cancel(Long id) {
Order order = orders.findById(id).orElseThrow();
order.cancel();
orders.save(order);
}
}

Nos testes, basta uma implementação em memória da interface.

Padrões relacionados​

  • Data Mapper — o Repository fica sobre ele e usa-o para persistir os objetos.
  • Query Object — usado para expressar consultas declarativas enviadas ao Repository.
  • Metadata Mapping — fornece o mapeamento usado pela implementação.
  • Unit of Work — registra as alterações feitas nos objetos obtidos pelo Repository.
  • Identity Map — garante que o Repository devolva a mesma instância para a mesma identidade.
  • Aggregate — o Repository trabalha por raiz de agregado.
  • Factory — cria os objetos novos, enquanto o Repository recupera os existentes.
  • Domain Model — o modelo de domínio acessa a persistência por meio do Repository.

Referência​