Desacoplando o acesso a dados da aplicação com o padrão Repository e Unit of Work - André Alves de Lima
Os padrões Repository e Unit of Work são design patterns muito conhecidos e a cada dia mais utilizados no desenvolvimento de aplicações de negócios. O grande problema é que a maioria dos exemplos que encontramos desses padrões é voltada para desenvolvimento web, ou utiliza uma tecnologia muito específica na implementação (por exemplo, Repository para Entity Framework). O objetivo do artigo de hoje é explicar os conceitos do padrão Repository e Unit of Work, listar as vantagens e desvantagens da sua utilização, bem como implementar um exemplo desses padrões em uma Console Application.
Eu sempre gosto de deixar bem claro nos meus artigos e vídeos quando eu estou falando de algo que eu não utilizo no dia a dia. Se você já consumiu algum conteúdo meu que não tenha uma seção de "disclaimer", isso significa que eu já coloquei em prática o assunto que foi abordado e que eu relativamente domino aquela área. Esse não é o caso do padrão Repository e Unit of Work, uma vez que eu nunca os utilizei em produção.
Até o momento em que eu parei para escrever esse artigo, eu nunca tinha nem parado para implementar um exemplo utilizando esses padrões! Eu já sabia exatamente o conceito por trás deles e as suas vantagens. Além disso, eu já tinha consumido inúmeros conteúdos de outras pessoas onde esses padrões tinham sido utilizados.
Dito isso, tudo o que eu vou mostrar no artigo de hoje foi o resultado do meu aprendizado nos últimos dias ao preparar um exemplo de Console Application utilizando os padrões Repository e Unit of Work. Essa Console Application será totalmente desacoplada da camada de acesso a dados concreta, possibilitando o armazenamento de dados tanto em memória quanto em um banco de dados (utilizando Entity Framework Code First).
Se você quiser conferir outros conteúdos de pessoas que aparentemente já têm mais experiência com esses padrões, eu recomendo que você dê uma olhada nos seguintes links (que me ajudaram como base na construção deste artigo):
– Creating a Data Repository using Dapper (indicação do leitor Fausto Luis)
– Ebook do Fabio Silva Lima sobre Design Patterns vol.1
– Repository Pattern with C# and Entity Framework, Done Right
– Quando usar Entity Framework com Repository Pattern?
– Entity Framework Repositório Genérico
O que é o padrão Repository?
Como mencionei anteriormente, o padrão Repository é um design pattern que pode ajudar muito quando implementamos o acesso a dados das nossas aplicações. Ele abstrai toda a parte de armazenamento de dados e consultas ao banco, servindo como um mediador entre a aplicação e a camada de mapeamento de objetos.
Quando utilizamos o padrão Repository, todas as nossas consultas customizadas são implementadas em um único local, evitando a repetição de código. Por exemplo, imagine que em uma determinada aplicação nós precisemos retornar todos os produtos de uma determinada categoria. Onde é que nós implementaríamos esse tipo de código? Direto na aplicação? E se nós precisarmos utilizar essa mesma consulta mais de uma vez? Como fazemos para não repetirmos o mesmo código em locais diferentes da aplicação?
Uma alternativa seria trabalharmos com uma camada de Data Access Objects, onde implementaríamos tanto os códigos do CRUD (criação, alteração, exclusão e listagem) quando as consultas customizadas. Outra opção seria trabalharmos com o padrão Repository, que é justamente o que eu vou mostrar para você no artigo de hoje.
O padrão Repository se resume basicamente a uma interface genérica, chamada de IRepository. Essa interface definirá o contrato dos métodos básicos de inclusão (Add), remoção (Remove) e listagem de registros (GetAll) que todo repositório concreto deverá implementar:
Nota: os nomes para os métodos podem variar dependendo da implementação. Algumas pessoas optam por "Create" ao invés de "Add" e "Delete" ao invés de "Remove". Eu escolhi esses nomes para manter o mesmo padrão das implementações de List (do .NET) e DbSet (do Entity Framework).
Uma vez estabelecido esse padrão, para cada entidade da nossa aplicação, nós teremos que criar um repositório concreto. Ou seja, se tivermos uma entidade "Produto", nós teremos que ter um repositório de produtos (ProdutoRepository). Se tivermos uma entidade "Cliente", precisaremos de um repositório para ele também (ClienteRepository), e assim por diante.
Dentro dos repositórios específicos, além dos métodos básicos de inclusão, remoção e listagem de registros, nós teremos os métodos de consultas customizadas. No exemplo que dei anteriormente do método que retornaria todos os produtos de uma determinada categoria, esse método seria definido na interface de repositório específica do produto (IProdutoRepository).
E o Unit of Work?
Note que na explicação acima sobre o padrão Repository, eu não falei nada sobre a parte de "salvar" as entidades. A responsabilidade do repositório é simplesmente armazenar uma coleção de entidades em memória. A persistência das informações não é de responsabilidade do repositório, mesmo porque, como conseguiríamos controlar transações feitas em mais de um repositório se a responsabilidade da persistência estivesse nesse elemento? Por exemplo, como faríamos para transacionar a operação de venda e baixa de estoque se a responsabilidade de salvar as informações estivesse separada nos repositórios de venda e estoque?
Para resolvermos esse problema, temos outro design pattern chamado Unit of Work. Esse padrão se resume a uma interface (IUnitOfWork) que terá todos os repositórios da aplicação, além de um método que será responsável por salvar as alterações (Save).
Na aplicação que iremos construir neste artigo, nós teremos dois repositórios. Um repositório conterá os produtos e outro repositório conterá as categorias de produtos. Dessa forma, a interface IUnitOfWork nesse projeto de exemplo terá os dois repositórios e o método "Save":
Não se preocupe se as coisas não estiverem claras até aqui. Eu também demorei um pouco para entender completamente esses dois padrões. Daqui a pouco nós veremos um exemplo concreto e tudo ficará mais claro.
Vantagens ao utilizar esses padrões
A primeira vantagem que temos ao utilizar os padrões Repository e Unit of Work é que, como mencionei anteriormente, as consultas complexas ficam todas armazenadas em um único local. Além disso, diferentemente dos DAOs, com o padrão Repository fica mais fácil de implementarmos um controle de acesso misto (onde, por exemplo, consultas seriam feitas com Dapper e o resto ficaria a cargo de algum ORM mais robusto).
Além disso, ao implementarmos esses padrões, nós podemos facilmente substituir de forma completa a camada de persistência de dados da nossa aplicação. Ou seja, nós poderíamos trocar o armazenamento de dados de um ORM para outro (do Entity Framework para o NHibernate, por exemplo) sem que o código central da aplicação precise ser modificado. Resumindo, a aplicação não tem a mínima ideia de onde os dados estão sendo armazenados, ela só conhece os repositórios genéricos.
Por fim, outra grande vantagem que temos ao utilizarmos repositórios é na parte de "testabilidade". Com o padrão Repository e Unit of Work, fica muito mais fácil criarmos "mocks" para testarmos as regras de negócio da nossa aplicação de forma independente do banco de dados.
Quando não devemos utiliza-los?
Esses padrões, apesar de serem muito úteis, trazem uma certa complexidade adicional ao nosso código. Se o domínio da sua aplicação não for complexo, não faz sentido adicionar toda essa parafernália.
Além disso, se você tiver certeza absoluta que você não precisará trocar a camada de persistência da sua aplicação, você pode continuar com os seus DAOs mesmo, uma vez que, com eles, você conseguirá atingir basicamente o mesmo benefício desses padrões.
Padrão Repository e Unit of Work genéricos na prática
Eu sei que tudo o que eu falei até agora está muito abstrato, mas acho que a partir desse momento as coisas começarão a ficar mais claras. Vamos implementar na prática o padrão Repository e Unit of Work em uma aplicação de exemplo que conterá duas entidades: Produtos e Categorias de Produtos. Tudo isso será feito em uma aplicação console e, como resultado final nós conseguiremos, com uma linha de código, trocar completamente a estratégia de armazenamento de dados da aplicação (em memória / no banco de dados em Entity Framework).
Para não nos confundirmos, nós iremos separar a implementação em projetos diferentes. Um projeto será a Console Application e, inicialmente, teremos um outro projeto que conterá as entidades e repositórios genéricos.
Então vamos começar criando um projeto do tipo Console Application e, na mesma solução, vamos adicionar um novo projeto do tipo "Class Library". Dentro desse projeto "Class Library", nós vamos adicionar as nossas duas entidades (CategoriaProduto e Produto):
public CategoriaProduto Categoria { get; set; }
Public Property Categoria As CategoriaProduto
Public Property CategoriaId As Integer
Public Property DataCadastro As DateTime
Em seguida, vamos adicionar uma nova pasta (chamada "Repository") no projeto "Class Library", onde colocaremos os repositórios genéricos da nossa aplicação. Dentro dessa pasta, vamos adicionar a nossa interface "IRepository", que terá a estrutura que mencionei anteriormente, com os métodos "GetAll", "Add" e "Remove":
Sub Remove(ByVal entity As T)
Nota: se você não sabe o que é esse "T" na definição da interface, ele está basicamente indicando que a interface é genérica, ou seja, ela é ligada a algum tipo. Nesse caso, os repositórios serão de um tipo específico (qualquer classe, como definido na cláusula "where"). No nosso caso, nós teremos repositório de produtos e categorias de produtos, como veremos logo a seguir. É a mesma ideia de listas genérica de algum tipo ("List<string>", por exemplo). Para mais informações sobre esse assunto, pesquise por "generics" no .NET.
Agora que já temos a nossa interface de repositório genérica, chegou a hora de definirmos as interfaces de repositório da nossa aplicação. No nosso caso, nós teremos uma interface de repositório para as categorias de produtos (ICategoriaProdutoRepository) e outra interface de repositório para os produtos (IProdutoRepository).
Na interface de repositório para as categorias de droduto, nós não teremos nenhum código customizado. Porém, na interface de repositório para os produtos, nós definiremos o contrato para o método que deverá retornar os produtos de uma determinada categoria (GetProdutosPorCategoria):
public interface ICategoriaProdutoRepository : IRepository<CategoriaProduto>
public interface IProdutoRepository : IRepository<Produto>
IEnumerable<Produto> GetProdutosPorCategoria(CategoriaProduto categoria);
Public Interface ICategoriaProdutoRepository
Inherits IRepository(Of CategoriaProduto)
Inherits IRepository(Of Produto)
Function GetProdutosPorCategoria(ByVal categoria As CategoriaProduto) As IEnumerable(Of Produto)
Por fim, só está faltando a definição do nosso Unit of Work. No nosso caso, dentro da nossa interface IUnitOfWork nós teremos uma instância de cada interface de repositório, além do método "Save":
ICategoriaProdutoRepository CategoriaProduto { get; }
IProdutoRepository Produto { get; }
ReadOnly Property CategoriaProduto As ICategoriaProdutoRepository
ReadOnly Property Produto As IProdutoRepository
E com isso nós finalizamos a implementação do nosso Repository e Unit of Work genéricos. Agora nós podemos desenvolver uma aplicação que só conhecerá essas interfaces, de forma que ela não tenha nenhuma dependência com uma implementação concreta. Vamos fazer isso no nosso projeto Console Application, que deverá ter uma referência para o projeto "Class Library", onde implementamos as entidades e o repositório genérico.
Dentro da classe "Program" nós definiremos uma instância da nossa interface "IUnitOfWork". Em seguida, dentro do método "main", nós faremos um workflow de listagem, criação, alteração e exclusão de produtos:
private static Model.Repository.IUnitOfWork contexto;
Private Contexto As Model.VB.Repository.IUnitOfWork
Contexto = Nothing
Nota: por enquanto nós setamos o valor do nosso contexto para "nulo", uma vez que nós ainda não temos nenhuma implementação concreta do repositório. Obviamente esse código não funcionará até o momento em que nós substituirmos essa declaração por uma implementação concreta do repositório.
Veja só o código dos métodos "List", "Create", "Update" e "Delete":
System.Console.WriteLine("======= LISTAGEM DE PRODUTOS =======");
foreach (var produto in contexto.Produto.GetAll())
System.Console.WriteLine("Id: {0}, Descricao: {1}, Categoria: {2}, Data Cadastro: {3:d}, Valor Unitário: {4:n2}",
produto.Id, produto.Descricao, produto.Categoria.Nome, produto.DataCadastro, produto.ValorUnitario);
var novoProduto = new Model.Produto();
novoProduto.Descricao = "Novo produto";
novoProduto.Categoria = contexto.CategoriaProduto.GetAll().First();
novoProduto.DataCadastro = DateTime.Now;
novoProduto.ValorUnitario = 2.34m;
contexto.Produto.Add(novoProduto);
System.Console.WriteLine("PRODUTO CADASTRADO COM SUCESSO");
var produto = contexto.Produto.GetAll().Last();
System.Console.WriteLine("PRODUTO ALTERADO COM SUCESSO");
contexto.Produto.Remove(produto);
System.Console.WriteLine("PRODUTO EXCLUÍDO COM SUCESSO");
System.Console.WriteLine("======= LISTAGEM DE PRODUTOS =======")
For Each Produto In Contexto.Produto.GetAll()
System.Console.WriteLine("Id: {0}, Descricao: {1}, Categoria: {2}, Data Cadastro: {3:d}, Valor Unitário: {4:n2}", Produto.Id, Produto.Descricao, Produto.Categoria.Nome, Produto.DataCadastro, Produto.ValorUnitario)
System.Console.WriteLine("====================================")
Dim NovoProduto = New Model.VB.Produto()
NovoProduto.Descricao = "Novo produto"
NovoProduto.Categoria = Contexto.CategoriaProduto.GetAll().First()
NovoProduto.DataCadastro = DateTime.Now
NovoProduto.ValorUnitario = 2.34D
Contexto.Produto.Add(NovoProduto)
Contexto.Save()
System.Console.WriteLine("PRODUTO CADASTRADO COM SUCESSO")
Dim Produto = Contexto.Produto.GetAll().Last()
Produto.Descricao = "Produto alterado"
System.Console.WriteLine("PRODUTO ALTERADO COM SUCESSO")
Contexto.Produto.Remove(Produto)
System.Console.WriteLine("PRODUTO EXCLUÍDO COM SUCESSO")
Observe que a aplicação não tem a mínima ideia de onde os dados serão armazenados. Ela só conhece as interfaces genéricas e não sabe de nenhuma implementação concreta do repositório.
Repository em memória
Agora que nós já temos a implementação da nossa aplicação, chegou a hora de criarmos um repositório concreto. O primeiro repositório concreto que criaremos armazenará os dados em memória, dentro de listas genéricas. Para não misturarmos as coisas, vamos criar um novo projeto "Class Library" onde faremos essa implementação. Esse novo projeto deverá referenciar o outro projeto onde as entidades foram definidas.
Dentro desse projeto, vamos adicionar uma classe chamada "Repository". Essa será a classe que servirá de base para todos os outros repositórios concretos. Vamos ver como é que fica o código dessa classe:
public abstract class Repository<T> : Model.Repository.IRepository<T> where T : class
protected List<T> _lista = new List<T>();
_lista.Add(entity);
return _lista.AsEnumerable();
_lista.Remove(entity);
Implements Model.VB.Repository.IRepository(Of T)
Protected Lista As List(Of T) = New List(Of T)
Public Overridable Sub Add(ByVal Entity As T) Implements Model.VB.Repository.IRepository(Of T).Add
Me.Lista.Add(Entity)
Public Overridable Function GetAll() As IEnumerable(Of T) Implements Model.VB.Repository.IRepository(Of T).GetAll
Return Me.Lista.AsEnumerable
Public Overridable Sub Remove(ByVal Entity As T) Implements Model.VB.Repository.IRepository(Of T).Remove
Me.Lista.Remove(Entity)
Observe que essa classe implementa a nossa interface genérica (IRepository), utilizando uma lista por trás dos panos, onde os dados serão armazenados.
Em seguida, vamos adicionar o nosso repositório concreto para as categorias dos produtos. Esse repositório não terá nenhum código customizado, ele será somente uma implementação concreta de "Repository<CategoriaProduto>":
public class CategoriaProdutoRepository : Repository<Model.CategoriaProduto>, Model.Repository.ICategoriaProdutoRepository
Public Class CategoriaProdutoRepository
Inherits Repository(Of Model.VB.CategoriaProduto)
Implements Model.VB.Repository.ICategoriaProdutoRepository
Por outro lado, o repositório concreto para os produtos deverá implementar duas lógicas adicionais. Uma delas é o método "GetProdutosPorCategoria", que foi definido no contrato da interface "IProdutoRepository". O outro código customizado é o cálculo do auto-incremento para o "Id" do Produto. Como nós não estamos salvando essas informações no banco de dados, nós temos que calcular o auto-incremento manualmente:
public class ProdutoRepository : Repository<Model.Produto>, Model.Repository.IProdutoRepository
public override void Add(Model.Produto entity)
entity.Id = GetMaxId() + 1;
public IEnumerable<Model.Produto> GetProdutosPorCategoria(Model.CategoriaProduto categoria)
return _lista.Where(p => p.Categoria == categoria).AsEnumerable();
if (_lista.Any())
maxId = _lista.Max(p => p.Id);
Inherits Repository(Of Model.VB.Produto)
Implements Model.VB.Repository.IProdutoRepository
Public Overrides Sub Add(ByVal Entity As Model.VB.Produto) Implements Model.VB.Repository.IProdutoRepository.Add
If (Entity.Id = 0) Then
Entity.Id = (Me.GetMaxId + 1)
Public Function GetProdutosPorCategoria(ByVal Categoria As Model.VB.CategoriaProduto) As IEnumerable(Of Model.VB.Produto) Implements Model.VB.Repository.IProdutoRepository.GetProdutosPorCategoria
Return Lista.Where(Function(p) p.Categoria Is Categoria).AsEnumerable
Private Function GetMaxId() As Integer
Dim MaxId = 0
If Lista.Any Then
MaxId = Lista.Max(Function(p) p.Id)
Por fim, a única coisa que está faltando é implementarmos a interface IUnitOfWork. Para isso, vamos adicionar uma nova classe no projeto, dando o nome de "UnitOfWork". Essa classe herdará da interface "IUnitOfWork" e terá instâncias dos repositórios concretos que acabamos de criar. Além disso, no construtor nós adicionaremos três categorias padrão na tabela de categorias de produtos:
public class UnitOfWork : Model.Repository.IUnitOfWork
private CategoriaProdutoRepository _categoriaProduto = new CategoriaProdutoRepository();
public Model.Repository.ICategoriaProdutoRepository CategoriaProduto { get { return _categoriaProduto; } }
private ProdutoRepository _produto = new ProdutoRepository();
public Model.Repository.IProdutoRepository Produto { get { return _produto; } }
_categoriaProduto.Add(new Model.CategoriaProduto() { Id = 1, Nome = "Categoria 1" });
_categoriaProduto.Add(new Model.CategoriaProduto() { Id = 2, Nome = "Categoria 2" });
_categoriaProduto.Add(new Model.CategoriaProduto() { Id = 3, Nome = "Categoria 3" });
Implements Model.VB.Repository.IUnitOfWork
Private _categoriaProduto As CategoriaProdutoRepository = New CategoriaProdutoRepository
Public ReadOnly Property CategoriaProduto As Model.VB.Repository.ICategoriaProdutoRepository Implements Model.VB.Repository.IUnitOfWork.CategoriaProduto
Return Me._categoriaProduto
Private _produto As ProdutoRepository = New ProdutoRepository
Public ReadOnly Property Produto As Model.VB.Repository.IProdutoRepository Implements Model.VB.Repository.IUnitOfWork.Produto
Return Me._produto
Me._categoriaProduto.Add(New Model.VB.CategoriaProduto)
Public Sub Save() Implements Model.VB.Repository.IUnitOfWork.Save
Nota: o método "Save" do UnitOfWork em memória não fará nada, uma vez que os dados não serão persistidos em lugar nenhum.
Agora que já temos a implementação concreta do repositório em memória, vamos voltar ao nosso projeto Console Application, onde faremos uso dessa implementação. Primeiramente, temos que adicionar uma referência à nova "Class Library" onde a implementação do repositório em memória foi realizada. Em seguida, basta alterarmos a declaração do "IUnitOfWork" para que a nossa classe "UnitOfWork" seja utilizada:
contexto = new InMemory.UnitOfWork();
Contexto = New InMemory.VB.UnitOfWork()
Repository com Entity Framework
OK, a nossa aplicação está funcionando perfeitamente com o armazenamento dos dados em memória. Mas, e se agora nós quisermos armazenar as informações em um banco utilizando Entity Framework? É aí que nós começaremos a enxergar as vantagens do padrão Repository e Unit of Work. Basta nós implementarmos o repositório com Entity Framework, trocar a declaração para que o novo UnitOfWork concreto seja utilizado e pronto! A aplicação nem saberá que os dados estão sendo armazenados no banco ao invés de em memória.
Vamos começar criando um novo projeto do tipo "Class Library" e adicionando a referência para o Entity Framework através do NuGet. Se ainda não conhece nada sobre o Entity Framework, dê uma olhada primeiro no meu artigo introdutório sobre o Entity Framework. Ele é leitura obrigatória se você ainda não conhece os conceitos básicos do Entity Framework.
Uma vez criado o projeto e adicionada a referência para o Entity Framework, vamos adicionar uma nova classe chamada "Contexto", que será o nosso contexto do Entity Framework:
internal class Contexto : System.Data.Entity.DbContext
public System.Data.Entity.DbSet<Model.CategoriaProduto> CategoriaProduto { get; set; }
public System.Data.Entity.DbSet<Model.Produto> Produto { get; set; }
public Contexto() : base()
if (!CategoriaProduto.Any())
CategoriaProduto.Add(new Model.CategoriaProduto() { Id = 1, Nome = "Categoria 1" });
CategoriaProduto.Add(new Model.CategoriaProduto() { Id = 2, Nome = "Categoria 2" });
CategoriaProduto.Add(new Model.CategoriaProduto() { Id = 3, Nome = "Categoria 3" });
Public Property CategoriaProduto As System.Data.Entity.DbSet(Of Model.VB.CategoriaProduto)
Public Property Produto As System.Data.Entity.DbSet(Of Model.VB.Produto)
If Not Me.CategoriaProduto.Any Then
Me.CategoriaProduto.Add(New Model.VB.CategoriaProduto)
Protected Overrides Sub OnModelCreating(ByVal ModelBuilder As System.Data.Entity.DbModelBuilder)
Como você pode observar, a única coisa que estamos fazendo de especial no contexto é a criação das categorias padrão (caso a tabela de categorias esteja vazia) e a desativação da pluralização das tabelas.
Com o contexto em mãos, chegou a hora de criarmos os nossos repositórios. Primeiramente, vamos adicionar uma classe chamada "Repository", que servirá como base para todos os repositórios concretos. Veja como fica a implementação dessa classe:
protected System.Data.Entity.DbSet<T> _dbSet;
public Repository(System.Data.Entity.DbSet<T> dbSet)
Protected DbSet As System.Data.Entity.DbSet(Of T)
Public Sub New(ByVal dbSet As System.Data.Entity.DbSet(Of T))
Me.DbSet = dbSet
Me.DbSet.Add(Entity)
Return Me.DbSet.AsEnumerable
Me.DbSet.Remove(Entity)
Note que a implementação desse repositório com o Entity Framework é muito parecida com a implementação em memória. A principal diferença é que nós estamos trabalhando por trás dos panos com um DbSet do Entity Framework (ao invés de um List em memória). O DbSet deverá ser passado por parâmetro no construtor.
Em seguida, vamos partir para a implementação dos repositórios CategoriaProdutoRepository e ProdutoRepository:
public CategoriaProdutoRepository(System.Data.Entity.DbSet<Model.CategoriaProduto> dbSet) : base(dbSet) { }
public ProdutoRepository(System.Data.Entity.DbSet<Model.Produto> dbSet) : base(dbSet) { }
return _dbSet.Where(p => p.Categoria == categoria).AsEnumerable();
Public Sub New(ByVal DbSet As System.Data.Entity.DbSet(Of Model.VB.CategoriaProduto))
MyBase.New(DbSet)
Public Sub New(ByVal DbSet As System.Data.Entity.DbSet(Of Model.VB.Produto))
Return DbSet.Where(Function(p) p.Categoria Is Categoria).AsEnumerable
Observe que dessa vez a implementação do repositório de produtos não precisa se preocupar com o cálculo do próximo "Id" do produto (como tivemos que fazer com o repositório em memória), uma vez que isso fica a cargo do Entity Framework.
Por fim, só está faltando a implementação da classe UnitOfWork. Veja como é que fica o código dela:
private Model.Repository.ICategoriaProdutoRepository _categoriaProduto;
public Model.Repository.ICategoriaProdutoRepository CategoriaProduto
return _categoriaProduto;
private Model.Repository.IProdutoRepository _produto;
public Model.Repository.IProdutoRepository Produto
_contexto = new Contexto();
_categoriaProduto = new CategoriaProdutoRepository(_contexto.CategoriaProduto);
_produto = new ProdutoRepository(_contexto.Produto);
Private Contexto As Contexto
Private _categoriaProduto As Model.VB.Repository.ICategoriaProdutoRepository
Private _produto As Model.VB.Repository.IProdutoRepository
Me.Contexto = New Contexto
Me._categoriaProduto = New CategoriaProdutoRepository(Me.Contexto.CategoriaProduto)
Me._produto = New ProdutoRepository(Me.Contexto.Produto)
Me.Contexto.SaveChanges()
Note que a implementação não é muito complicada. Nós só temos as variáveis e propriedades para cada um dos repositórios, além de uma variável para o contexto do Entity Framework. Os repositórios são instanciados no construtor passando os respectivos DbSets do contexto. Além disso, o método "Save" simplesmente chama um "SaveChanges" no contexto do Entity Framework.
E agora chegou a hora da grande "mágica". Se nós adicionarmos uma referência no nosso projeto Console Application para essa nova "Class Library" onde o repositório do Entity Framework foi implementado, nós conseguimos trocar completamente a estratégia de armazenamento de dados para a nossa aplicação. Ao invés de armazenarmos em memória, nós armazenaremos no banco de dados utilizando o Entity Framework:
contexto = new EF.UnitOfWork();
Contexto = New EF.VB.UnitOfWork()
E o banco de dados foi criado automaticamente por trás dos panos no LocalDb:
Dificuldades do Repository com ADO.NET puro ou Dapper
Será que é possível utilizarmos o padrão Repository e Unit of Work com ADO.NET puro ou Dapper? Apesar de ser possível, nós teremos algumas dificuldades se quisermos seguir esse padrão em que o repositório não é responsável pela persistência dos dados, mas sim o Unit of Work. Nesse caso teríamos que controlar o estado das entidades para vermos se elas foram alteradas, e aí sim fazermos um INSERT, UPDATE ou DELETE no banco de dados dependendo das alterações que foram feitas.
Uma alternativa seria trazer a responsabilidade da atualização dos dados para o repositório (ou seja, nós teríamos um método "Update" nele também), descartando então o Unit of Work. Porém, isso tornaria a implementação meio que incompatível com ORMs como Entity Framework e NHibernate.
Quem sabe em uma próxima oportunidade eu tente explorar essas duas possibilidades, mas hoje nós vamos ficando por aqui.
O armazenamento de informações é uma parte crucial de qualquer aplicação de negócios. Muitas vezes, nós acabamos implementando a aplicação de maneira completamente acoplada a uma estratégia de persistência de dados (ADO.NET, Entity Framework, NHibernate, Dapper, etc). Aplicações desenvolvidas dessa maneira funcionam muito bem até o momento em que precisamos trocar a camada de armazenamento de dados.
Para desacoplarmos o acesso a dados da aplicação, nós podemos utilizar o padrão Repository e Unit of Work. Outra vantagem que temos ao utilizarmos esses padrões é a concentração de consultas customizadas em um único lugar, evitando repetição de código.
Você já trabalhou com esses design patterns? Como foram as suas experiências com eles? Eu mencionei no início do artigo que eu nunca trabalhei profissionalmente com eles, mas depois de ter escrito esse artigo eu consegui entender a importância deles e muito provavelmente vou acabar utilizando esses padrões em novas aplicações no futuro. Fico aguardando as suas observações nos comentários logo abaixo.
Revisão de 2017, metas para 2018 Comparando a data/hora com um servidor NTP no .NET
8 thoughts on "Desacoplando o acesso a dados da aplicação com o padrão Repository e Unit of Work"
Parabéns pelo conteúdo….seria bom se tivesse prosseguimento no mesmo tema, antes de ir para um próximo….para o aluno não perder o foco!!
15 de janeiro de 2018 às 6:00
Infelizmente não vou conseguir dar continuidade com esse tema nas próximas semanas, uma vez que eu já tinha outros conteúdos prontos e agendados.. Mas, pode ficar tranquilo que já está nos meus planos a segunda parte desse artigo..
Muito Bom. Iniciantes devem ter ficado com "água na boca"…
Em relação a "… teríamos que controlar o estado das entidades para vermos se elas foram alteradas, e aí sim fazermos um INSERT, UPDATE ou DELETE no banco de dados dependendo das alterações que foram feitas.", penso que o artigo + comentários em "https://www.codeproject.com/Articles/581487/Unit-of-Work-Design-Pattern" poderá ser esclarecedor.
Um ou outro pormenor que poderá querer incluir na sua agenda:
– DI (Direct Injection);
– UoW genérico () para quando temos muitas tabelas/POCO's para gerir;
– Factory para gerir conexões (através da leitura do app.config, p. exº, podermos escolher com facilidade, qual o data provider que iremos utilizar (com recurso ao IDbConnection, p. exº));
– Implementação no repositório UnitOfWork, da operação Save/Commit/Complete e consequente Rollback.
17 de janeiro de 2018 às 5:58
Muito obrigado pelo comentário e pelas sugestões de temas.. Vou colocá-los aqui na minha lista.. :)
O InMemory encontramos em qual lib/namespace?
"InMemory" foi só o nome do projeto que eu escolhi para colocar a implementação do repositório que só persiste as informações em memória.. Não é nenhuma referência ou namespace específico do .NET..
11 de dezembro de 2018 às 22:05
Eu tenho uma dúvida, utilizando Entity Framework seria correto criar um método no repositório que retorne um IQueryable?
Se não, o que seria recomendado caso eu queira fazer Join entre as entidades onde as mesmas não possuem Navigation Properties?
Vou ser sincero com você.. Eu particularmente não tenho calibre (falta experiência) para conseguir te responder essa sua dúvida.. Mas, eu já vi em muitos lugares que isso (retornar IQueryable em repositório) é uma má prática.. Tem uma discussão sobre esse tema no StackOverflow, talvez te ajude: