Repositório ou Não Repositório

10

Quando aprendi sobre o Domain Driven Design, também fui apresentado aos padrões de repositório e unidade de trabalho que antes pareciam ser de primeira qualidade para as crianças legais que lançavam consultas SQL, como homens das cavernas, em bancos de dados. Quanto mais aprofundado esse tópico, mais aprendi que eles não parecem mais necessários por causa de ORMs como EF e NHibernate que implementam a unidade de trabalho e os repositórios em uma API, chamada sessão ou contexto.

Agora não tenho certeza do que fazer. Repositório ou não Repositório. Eu realmente entendo o argumento de que tais abstrações com vazamentos apenas complicam demais as coisas, ao mesmo tempo em que não adicionam absolutamente nada que possa simplificar o acesso aos dados; no entanto, não parece correto associar todos os aspectos possíveis do meu aplicativo ao, por exemplo, o Entity Framework . Geralmente, sigo algumas diretrizes simples:

  1. A camada de domínio é o coração do sistema, contendo entidades, serviços, repositórios ...
  2. A camada de infraestrutura fornece implementações de interfaces de domínio de uma preocupação de infraestrutura, por exemplo, arquivo, banco de dados, protocolos.
  3. A camada de aplicativo hospeda uma raiz de composição que conecta as coisas e orquestra tudo.

Minhas soluções geralmente são assim:

Domain.Module1
Domain.Module2
    IModule2Repo
    IModule2Service
    Module2
Infrastructure.Persistence
    Repositories
        EntityFrameworkRepositoryBase
MyApp
    Boostrapper
        -> inject EntityFrameworkRepositoryBase into IRepository etc.

Eu mantenho minha camada de domínio limpa usando uma IRepository<'T>que também é uma preocupação de domínio, não depende de mais nada que me diga como acessar dados. Quando agora eu faria uma implementação concreta IModule2Serviceque requer acesso a dados, teria que injetar DbContexte, por isso, acoplá-lo diretamente à camada de infraestrutura. ( Chegando ao projeto do Visual Studio, isso pode ser realmente complicado por causa de dependências circulares! )

Além disso, o que pode ser uma alternativa para depositários e fucktons de obras ? CQRS? Como alguém abstrai uma estrutura infra-estrutural pura?

Acrotygma
fonte
11
Como um aparte, se você está falando sobre 'Repositório' como descrito em DDD, EF e nHibernate não são repositórios. Claro, eles abstraem parcialmente o mecanismo de acesso a dados, mas há muito mais em um repositório do que isso .
Eric Rei

Respostas:

4

Engenharia é tudo sobre compromissos. E o mesmo acontece com o desenvolvimento de software. No momento, acredito que apenas outra opção, que seria mais simples, é trabalhar diretamente com o ORM. Mas, como você disse, isso pode prendê-lo a uma estrutura de persistência específica.

Portanto, você deve se perguntar "A complexidade adicional vale a dissociação do seu código da persistência". Toda vez que ouço as pessoas dizerem que desejam separar seu código da persistência, pergunto: "Quantas vezes em sua carreira você mudou sua estrutura de persistência?"

O problema que vejo nos repositórios é que eles dificultam as alterações comuns. Por exemplo. adicionando nova maneira de consultar um agregado. E eles facilitam mudanças incomuns (supostamente). Por exemplo. mudança na implementação de repositórios.

Depois, há também o argumento de teste de unidade, ao qual digo "Se a estrutura de persistência não permite que você zombe de um banco de dados, na memória ou localmente, não vale a pena usar a estrutura".

Eufórico
fonte
11
O objetivo do repositório é separar o aplicativo da persistência e não há complexidade envolvida. E altero os detalhes da persistência pelo menos uma vez, porque o acesso ao banco de dados é a última coisa que codifico, até o momento em que estou usando em repositórios de memória. Além disso, há uma armadilha muito sutil quando você está usando um detalhe de persistência diretamente. Seus objetos de negócios tendem a ser projetados para serem compatíveis com eles, em vez de agnósticos. E isso é porque uma rica entidade, devidamente encapsulados não podem ser diretamente restaurado a partir de qualquer armazenamento (exceto memória) sem (alguns feio) Soluções Alternativas
MikeSW
Sobre como adicionar uma nova maneira de consultar uma raiz agregada ou qualquer entidade, é por isso que o CQRS apareceu. Pessoalmente, mantenho o Repositório para fins de domínio e uso manipuladores de consultas para consultas reais. E esses manipuladores são fortemente acoplados ao db e ofc muito eficientes no que fazem.
6135 MikeSW
3

Sou pró-repositório, embora tenha me afastado dos padrões genéricos de repositório. Em vez disso, alinhei meus repositórios com a função comercial que eles servem. Os repositórios não têm como objetivo abstrair o ORM, pois isso não é algo que eu espero que mude e, ao mesmo tempo, evito tornar um repositório muito granular. (Ou seja, CRUD) Em vez disso, meus repositórios servem de dois a três objetivos principais:

  • Recuperação de dados
  • Criação de dados
  • Exclusão definitiva

Para recuperação de dados, o repositório sempre retorna IQueryable<TEntity>. Para criação de dados, ele retorna TEntity. O repositório manipula minha filtragem no nível de base, como o estado "ativo" de autorização para sistemas que usam padrões de exclusão reversível e o estado "atual" para sistemas que usam dados históricos. A criação de dados é responsável apenas por garantir que as referências necessárias sejam resolvidas e associadas e que a entidade esteja configurada e pronta para uso.

A atualização de dados é de responsabilidade da lógica comercial que trabalha com as entidades em questão. Isso pode incluir coisas como regras de validação. Não tento encapsular isso em um método de repositório.

A exclusão na maioria dos meus sistemas é exclusão reversível para que caísse na atualização de dados. (IsActive = false) No caso de exclusões físicas, isso seria uma linha única no Repositório.

Por que repositórios? Capacidade de teste. Claro, o DbContext pode ser ridicularizado, mas é mais simples zombar de uma classe que retornaIQueryable<TEntity>. Eles também funcionam bem com o padrão UoW, pessoalmente eu uso o padrão DbContextScope do Mehdime para definir a unidade de trabalho no nível que eu quero (controladores no MVC) e permitir que meus controladores e classes de serviço auxiliar utilizem os repositórios sem precisar passar referências para o UoW / dbContext ao redor. Usar IQueryable significa que você não precisa de muitos métodos de wrapper no repositório, e seu código pode otimizar como os dados serão consumidos. Por exemplo, o repositório não precisa expor métodos como "Exists" ou "Count" ou tentar agrupar entidades com outros POCOs nos casos em que você deseja subconjuntos de dados. Eles nem precisam lidar com opções de carregamento rápido para dados relacionados que você pode ou não precisar. Ao passar IQueryable, o código de chamada pode:

.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()

Muito flexível e, de um PoV de teste, meu repositório simulado simplesmente precisa retornar Listas de objetos de entidade preenchidos.

Quanto aos repositórios genéricos, eu me afastei deles na maior parte porque, quando você termina com um repositório por tabela, seus controladores / serviços terminam com referências a vários repositórios para executar uma ação comercial. Na maioria dos casos, apenas um ou dois desses repositórios estão realmente realizando operações de gravação (desde que você esteja usando as propriedades de navegação corretamente), enquanto o restante suporta aqueles com Reads. Prefiro ter algo como um OrdersRepository capaz de ler e criar pedidos, além de ler quaisquer pesquisas relevantes etc. (objetos leves do cliente, produtos etc.) como referência ao criar um pedido, em vez de atingir 5 ou 6 repositórios diferentes. Pode violar os puristas do DNRY, mas meu argumento é que o objetivo do repositório é servir à criação de Pedidos, que inclui as referências relacionadas.Repository<Product>para obter produtos nos quais, com base em um pedido, só preciso de uma entidade com um punhado de campos. Meu OrderRepository pode ter um .GetProducts()método retornando, IQueryable<ProductSummary>que eu acho mais agradável que um Repository<Product>que acaba tendo vários métodos "Get" para tentar atender a diferentes áreas das necessidades do aplicativo e / ou alguma expressão complexa de filtragem de passagem.

Opto por um código mais simples, fácil de seguir, testar e ajustar. Pode ser abusado de maneiras ruins, mas eu prefiro ter algo em que os abusos sejam fáceis de identificar e corrigir do que tentar "bloquear" o código de uma maneira que não possa ser abusada, falhar e ter algo que seja um pesadelo para realmente fazer o que o cliente paga para fazê-lo no final. :)

Steve Py
fonte