Como usar o padrão de repositório corretamente?

86

Estou me perguntando como devo agrupar meus repositórios? Como nos exemplos que vi no asp.net mvc e em meus livros, eles usam basicamente um repositório por tabela de banco de dados. Mas isso parece ser um monte de repositórios levando você a ter que chamar muitos repositórios mais tarde para simulação e outras coisas.

Então, acho que devo agrupá-los. No entanto, não tenho certeza de como agrupá-los.

Agora mesmo fiz um repositório de registro para lidar com todas as minhas coisas de registro. No entanto, existem cerca de 4 tabelas que preciso atualizar e antes eu tinha 3 repositórios para fazer isso.

Por exemplo, uma das tabelas é uma tabela de licença. Quando eles se registram, eu olho sua chave e verifico se existe no banco de dados. Agora, o que acontece se eu precisar verificar esta chave de licença ou alguma outra coisa daquela tabela em algum outro local diferente do registro?

Um ponto pode ser o login (verifique se a chave não expirou).

Então, o que eu faria nesta situação? Reescrever o código novamente (quebrar DRY)? Tente mesclar esses 2 repositórios e espero que nenhum dos métodos seja necessário em algum outro momento (como talvez eu possa ter um método que verifica se userName é usado - talvez eu precise disso em outro lugar).

Além disso, se eu mesclá-los, precisaria de 2 camadas de serviço indo para o mesmo repositório, pois acho que ter toda a lógica para 2 partes diferentes de um site seria longo e eu teria que ter nomes como ValidateLogin (), ValdiateRegistrationForm () , ValdiateLoginRetrievePassword () e etc.

Ou ligar para o Repositório de qualquer maneira e apenas ter um nome estranho por aí?

Simplesmente parece difícil fazer um repositório com um nome geral o suficiente para que você possa usá-lo para muitos pontos de sua aplicação e ainda faça sentido e eu não acho que chamar outro repositório em um repositório seria uma boa prática?

chobo2
fonte
11
+1. Ótima pergunta.
griegs
Obrigado, isso está me incomodando há algum tempo. Como os que vi no livro são muito simples, eles não mostram o que fazer nessas situações.
chobo2
Estou basicamente fazendo o mesmo que você e sim, se eu tiver uma classe linq2sql que é usada em mais de um repositório e precisar alterar a estrutura da tabela, eu quebro o DRY. Menos que o ideal. Agora eu planejo um pouco melhor, então não preciso usar uma classe linq2sql mais de uma vez, o que eu acho que é uma boa separação de preocupações, mas prevejo um dia em que isso será um problema real para mim.
griegs
Eu fiz uma pergunta semelhante (mas não idêntica) aqui: stackoverflow.com/questions/910156/…
Grokys
Sim, mas parece difícil planejá-lo para todas as situações. Como eu disse, posso mesclar talvez o login e o registro em uma autenticação e ter 2 camadas separadas. Isso provavelmente resolverá meu problema. Mas o que acontece se, digamos, na página de perfil do meu site, eu quiser mostrar a chave deles por qualquer motivo (talvez eu deixe ele mudar isso ou algo assim). Agora o que faço quebre o DRY e escrevo a mesma coisa? Ou tente fazer um repositório que possa de alguma forma caber todas as 3 dessas tabelas com um bom nome.
chobo2

Respostas:

41

Uma coisa que eu fiz de errado quando brinquei com o padrão de repositório - assim como você, pensei que a tabela se relacionasse com o repositório 1: 1. Quando aplicamos algumas regras do Domain Driven Design - o problema de agrupamento de repositórios geralmente desaparece.

O repositório deve ser por raiz agregada e não por tabela. Isso significa - se a entidade não deve viver sozinha (ou seja - se você tem um Registrantparticipante em particular Registration) - é apenas uma entidade, não precisa de um repositório, deve ser atualizada / criada / recuperada através do repositório de raiz agregada dela pertence.

Claro - em muitos casos, esta técnica de reduzir a contagem de repositórios (na verdade - é mais uma técnica para estruturar seu modelo de domínio) não pode ser aplicada porque cada entidade deve ser uma raiz agregada (que depende muito de seu domínio, Posso fornecer apenas palpites cegos). Em seu exemplo - Licenseparece ser uma raiz agregada porque você precisa ser capaz de verificá-los sem contexto de Registrationentidade.

Mas isso não nos restringe a repositórios em cascata (o Registrationrepositório pode fazer referência ao Licenserepositório, se necessário). Isso não nos restringe a referenciar Licenserepositório (preferível - por meio de IoC) diretamente do Registrationobjeto.

Apenas tente não conduzir seu projeto por complicações fornecidas por tecnologias ou por mal-entendidos. Agrupar repositórios ServiceXapenas porque você não quer construir 2 repositórios não é uma boa ideia.

Muito melhor seria dar-lhe um nome próprio - RegistrationServiceou seja,

Mas os serviços devem ser evitados em geral - eles geralmente são a causa que leva ao modelo de domínio anêmico .

EDIT:
Comece a usar IoC. Ele realmente alivia a dor de injetar dependências.
Em vez de escrever:

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

você será capaz de escrever:

var registrationService = IoC.Resolve<IRegistrationService>();

Ps Seria melhor usar o chamado localizador de serviço comum, mas isso é apenas um exemplo.

Arnis Lapsa
fonte
17
Hahaha ... Estou dando conselhos para usar o localizador de serviço. É sempre uma alegria ver como fui burro no passado.
Arnis Lapsa,
1
@Arnis Parece que suas opiniões mudaram - estou interessado em como você responderia a essa pergunta de forma diferente agora?
ngm
10
@ngm mudou muito desde que respondi a isso. Ainda concordo que a raiz agregada deve traçar limites transacionais (ser salvos como um todo), mas estou muito menos otimista sobre a abstração da persistência usando o padrão de repositório. Ultimamente - estou apenas usando ORM diretamente, porque coisas como gerenciamento de carregamento ansioso / preguiçoso são muito difíceis. É muito mais benéfico focar no desenvolvimento de um modelo de domínio rico em vez de focar na persistência abstrata.
Arnis Lapsa,
2
@Developer não, não exatamente. eles ainda devem ser persistentes ignorantes. você recupera a raiz agregada de fora e chama o método que faz o trabalho. meu modelo de domínio não tem referências, apenas algumas da estrutura .net padrão. para conseguir isso, você deve ter um modelo de domínio rico e ferramentas que são inteligentes o suficiente (NHibernate faz o truque).
Arnis Lapsa
1
Pelo contrário. Buscar mais de um ggregate geralmente significa que você pode usar o PK ou índices. Muito mais rápido do que aproveitar as relações que a EF gera. Quanto menores forem os agregados de raiz, mais fácil será obter desempenho neles.
jgauffin
5

Uma coisa que comecei a fazer para resolver isso é realmente desenvolver serviços que envolvam N repositórios. Esperançosamente, seus frameworks DI ou IoC podem ajudar a tornar isso mais fácil.

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

Isso faz sentido? Além disso, eu entendo que falar de serviços nesta mansão pode ou não estar de acordo com os princípios do DDD, eu só faço isso porque parece funcionar.

neouser99
fonte
1
Não, isso não faz muito sentido. Não uso frameworks DI ou IoC no momento porque tenho o suficiente para fazer.
chobo2
1
Se você pode instanciar seus repos com apenas um new (), você pode tentar isso ... public ServiceImpl (): this (new Repo1, new Repo2 ...) {} como um construtor adicional no serviço.
neouser99
Injeção de dependência? Eu já faço isso, mas ainda não tenho certeza de qual é o seu código e o que ele resolve.
chobo2
Vou especificamente atrás da fusão de repositórios. Qual código não faz sentido? Se você estiver usando DI, então o código na resposta funcionará no sentido de que sua estrutura de DI injetará esses serviços IRepo, no código de comentário é basicamente apenas uma pequena solução alternativa para fazer DI (basicamente seu construtor sem parâmetro 'injeta' essas dependências em seu ServiceImpl).
neouser99
Eu já uso o DI para poder testar melhor a unidade. Meu problema é que se você fizer suas camadas de serviço e repositórios com detalhes de nomes. Então, se você precisar usá-los em qualquer outro lugar, parecerá estranho chamar como a camada RegistrationService que chama o RegRepo em alguma outra classe, digamos como ProfileClass. Portanto, não estou vendo de seu exemplo o que está fazendo plenamente. Por exemplo, se você começar a ter muitos Repos na mesma camada de serviço, terá uma lógica de negócios e uma lógica de validação muito diferentes. Já que na camada de serviço você costuma colocar a lógica de validação. Tantos que preciso de mais ...
chobo2
2

O que estou fazendo é ter uma classe base abstrata definida da seguinte forma:

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

Você pode então derivar seu repositório da classe base abstrata e estender seu único repositório, desde que os argumentos genéricos sejam diferentes, por exemplo;

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

etc ....

Eu preciso de repositórios separados porque temos restrições em alguns de nossos repositórios e isso nos dá o máximo de flexibilidade. Espero que isto ajude.

Michael Mann
fonte
Então você está tentando fazer um repositório genérico para lidar com tudo isso?
chobo2
Então, o que realmente vai dizer o método Update. Como se você tivesse esse V update e ele passasse em um T entitytoUpdate, mas não há nenhum código de fato atualizando-o ou existe?
chobo2
Sim .. Haverá código no método de atualização porque você escreverá uma classe que desce do repositório genérico. O código de implementação pode ser Linq2SQL ou ADO.NET ou o que você escolheu como tecnologia de implementação de acesso a dados
Michael Mann
2

Eu tenho isso como minha classe de repositório e sim, eu estendo no repositório de tabela / área, mas ainda às vezes tenho que quebrar o DRY.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

EDITAR

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

Eu também tenho um datacontext que foi criado usando a classe Linq2SQL.dbml.

Portanto, agora eu tenho um repositório padrão implementando chamadas padrão como All e Find e em meu AdminRepository tenho chamadas específicas.

Não responde à pergunta de DRY, embora eu não ache.

griegs
fonte
Para que serve "Repositório"? e gosta de CreateInstance? Não tenho certeza do que tudo que você tem está fazendo.
chobo2
É genérico como qualquer coisa. Basicamente, você precisa ter um repositório específico para sua (área). Verifique a edição acima para meu AdminRepository.
griegs
1

Aqui está um exemplo de uma implementação de Repositório genérico usando FluentNHibernate. Ele é capaz de persistir qualquer classe para a qual você escreveu um mapeador. É ainda capaz de gerar seu banco de dados com base nas classes do mapeador.

James Jones
fonte
1

O padrão Repositório é um padrão de design ruim. Eu trabalho com muitos projetos .Net antigos e esse padrão normalmente causa erros "Transações distribuídas", "Reversão parcial" e "Pool de conexões esgotado" que podem ser evitados. O problema é que o padrão tenta lidar com conexões e transações internamente, mas essas devem ser feitas na camada do controlador. Além disso, o EntityFramework já abstrai muito da lógica. Eu sugeriria usar o padrão de serviço em vez de reutilizar o código compartilhado.

ColacX
fonte
0

Eu sugiro que você dê uma olhada em Sharp Architecture . Eles sugerem o uso de um repositório por entidade. Estou usando atualmente em meu projeto e estou muito satisfeito com os resultados.

Astuto
fonte
Eu daria um +1 aqui, mas ele indicou que os contêineres DI ou IoC não são uma opção (desde que esses não sejam os únicos benefícios do Sharp Arch). Eu estou supondo que existe algum código em torno do qual ele está trabalhando.
neouser99
O que é considerado uma entidade? Esse é o banco de dados inteiro? ou é uma tabela de banco de dados? Os containers DI ou IoC não são uma opção no momento, já que não quero aprender isso além das outras 10 coisas que estou aprendendo ao mesmo tempo. eles são algo que analisarei em minha próxima revisão do meu site ou meu próximo projeto. Mesmo que eu não tenha certeza se será este, uma rápida olhada no site parece querer que você use o nhirbrate e eu estou usando linq para sql no momento.
chobo2