Executando verificação de segurança...
10

Arquitetura de Software em C# no Mundo Real: Capítulo 7 – Repositórios que respeitam o domínio (e evitam vazamento de infra)

Essa série de artigos nasceu da frustração — e da prática.

Se você já tentou organizar um sistema .NET complexo, sabe que não é fácil: cada time tem uma visão diferente, os tutoriais se contradizem, e os “padrões” muitas vezes atrapalham mais do que ajudam. Entre um GenericRepository<T> e um PaymentServiceHandlerProcessorFactory, fica difícil saber o que realmente importa.

Aqui, a proposta é simples: falar sobre arquitetura real, com um olhar crítico, direto e sem medo de questionar modinhas. Vamos explorar estilos, padrões, anti-padrões e armadilhas comuns que se acumulam ao longo dos anos — e mostrar alternativas que funcionam fora do slide de apresentação.

Essa série é para desenvolvedores que já sujaram as mãos. Que já herdaram legado, quebraram produção, e tentaram explicar DDD em reunião de grooming.

Sem fórmulas mágicas. Sem purismo. Só código, contexto e experiência real.


Índice da Série

Capítulo 1 – Os caminhos que a arquitetura pode tomar

Um panorama direto e sem rodeios sobre Transaction Script, Modelo Anêmico, Modelo Rico e DDD.

Capítulo 2 – O modelo anêmico vai te trair (e como saber quando fugir dele)

Quando usar um modelo anêmico parece mais simples, mas te afunda em bugs e complexidade acidental.

Capítulo 3 – A falsa promessa do “service pattern” genérico

AccountService, UserManager, FooHandler: nomes que escondem baixa coesão e design acoplado.

Capítulo 4 – Entidades ricas: encapsulamento é mais que private set;

Como construir entidades com comportamento real e proteger sua integridade.

Capítulo 5 – A camada de aplicação não é um lixo eletrônico

Application Services devem orquestrar, não conter regras de negócio nem virar dumping ground de dependências.

Capítulo 6 – Use Cases, Command Handlers e MediatR: o que separa clareza de confusão

Quando aplicar CQRS traz benefícios reais — e quando só adiciona complexidade desnecessária.

Capítulo 7 – Repositórios que respeitam o domínio (e evitam vazamento de infra)

Um repositório não é só um wrapper de DbContext. É um contrato do domínio com a persistência.

Capítulo 8 – Domínio expressivo: Value Objects, Invariantes e Eventos de Domínio

Modelando regras com segurança e intenção. O domínio deve falar por si.

Capítulo 9 – Dividindo para conquistar: a importância dos Bounded Contexts

DDD real exige saber onde termina um modelo e começa outro. E como manter essa separação.

Capítulo 10 – Anti-patterns arquiteturais que todo dev .NET já viu (ou cometeu)

Um olhar honesto sobre os maiores pecados que já cometemos em nome da “arquitetura”.

Capítulo 11 – Mitos e Más Interpretações na Arquitetura .NET

Por que MVC não é arquitetura, repositório não é para toda entidade, padrão não é enfeite, e SOLID não é carta branca pra travar refatoração.

Capítulo 7 – Repositórios que respeitam o domínio (e evitam vazamento de infra)

Se tem um padrão que apanha muito na prática é o tal do Repository.
Ele aparece em toda arquitetura “DDD-like”, geralmente como um IRepository<T> com meia dúzia de métodos (Add, Update, Delete, GetById, GetAll).

Mas vamos ser honestos: isso é um DAL com outro nome.

Se o seu repositório é só um wrapper genérico em cima do Entity Framework, ele não está servindo ao domínio. Está servindo à infraestrutura.
E pior: está vazando detalhes da persistência para fora da camada que deveria estar protegida.


O problema do GenericRepository<T>

Essa abstração parece útil. Reaproveitável. DRY.
Mas o custo disso é altíssimo.

Imagine um GenericRepository<Trade> com método FindByCondition(Expression<Func<Trade,bool>> condition).
O que impede o application service de escrever uma query diretamente ali dentro, baseada em EF Core?

Nada.

O domínio passa a depender de expressões de LINQ, tipagem de EF, DbSet, AsNoTracking, Include, etc.

Resultado?
Você abstraiu o DbContext, mas espalhou EF para o domínio inteiro.


Repositório é um contrato do domínio, não um DSL de banco

Um bom repositório tem uma interface específica para o agregado que representa.
E a assinatura dos métodos fala a linguagem do domínio.

Veja o contraste:

// Interface genérica (anti-padrão)
public interface IRepository<T>
{
    Task<T?> GetByIdAsync(Guid id);
    Task<List<T>> FindByCondition(Expression<Func<T, bool>> condition);
}
// Interface rica e explícita (preferível)
public interface ITradeRepository
{
    Task<Trade?> GetPendingByIdAsync(Guid tradeId);
    Task<List<Trade>> GetByFirmAndStatus(string firm, TradeStatus status);
    void Add(Trade trade);
}

Percebe a diferença?

O repositório agora conhece o negócio. Ele existe para atender casos de uso específicos.

E com isso, você ganha:

  • Intellisense mais útil
  • Testes mais fáceis (mock de métodos claros)
  • Menos vazamento de infra
  • Evolução mais segura (você não quebra tudo ao mudar um campo no banco)

Repositório é para agregados, não para entidades avulsas

No DDD, o repositório representa um Aggregate Root.

Isso quer dizer:
Você não precisa de repositório para tudo.
Você não precisa de um CustomerRepository, AddressRepository, PhoneRepository...

Você precisa de CustomerRepository se Customer é um Aggregate Root que contém Address e Phone.

A operação é sempre no agregado inteiro. Nunca em pedaços.

var customer = await _customerRepository.GetByEmail(email);
customer.UpdatePhone(newPhone);
await _unitOfWork.Commit();

Isso é o modelo funcionando como o DDD propõe.


Como testar repositórios?

Você pode manter o repositório com EF por baixo. Mas:

  • Evite deixar ele exposto no domínio (ex: não injete _dbContext em handler)
  • Escreva testes unitários para os métodos do repositório (mockando o DbContext ou usando EF in-memory)
  • Escreva testes de integração para garantir que as queries refletem o modelo real

Mas não caia na armadilha de mockar LINQ com Expression<Func<T,bool>>.
Teste as queries de verdade ou mantenha-as como privadas no repositório.


Dica prática: agrupe queries em ReadModels quando fizer sentido

Nem tudo precisa ir para o repositório do domínio.

Se sua query serve só para exibir dados na tela (ex: um dashboard), crie um ITradeReadModel separado.
Nele, você pode usar Dapper, LINQ, joins — o que quiser — sem poluir o modelo de domínio.

É o espírito do CQRS:

  • O domínio cuida da escrita,
  • Os ReadModels cuidam da leitura.

Conclusão

Repositórios não são para reutilização genérica.
Eles são contratos do domínio com a persistência, e devem falar a linguagem da sua aplicação.

Ao projetar bem o repositório, você:

  • Protege o domínio de detalhes de infra
  • Ganha confiança ao refatorar
  • Reduz o acoplamento implícito
  • E prepara o terreno para evoluir sem dor

Próximo capítulo:
E se estamos falando de proteger o modelo, então precisamos falar de um dos maiores aliados nessa missão: os Value Objects.

Carregando publicação patrocinada...