Desenvolvimento

6 out, 2010

Persistência plugável

Publicidade

Em agosto, fui palestrante em um evento em que apresentei o conceito de persistência plugável. Depois da palestra, recebi alguns pedidos para construir uma demonstração da substituição
do Entity Framework 4 pelo NHibernate. É importante ressaltar que esse
procedimento é possível graças à junção de várias técnicas já
conhecidas, ou seja, não estamos criando nada, apenas montando uma nova
receita com ingredientes já conhecidos.

Outro ponto que gostaria de ressaltar é que os conceitos aqui
apresentados podem (e devem) ser aplicados em vários aspectos de um
projeto, não só na camada de persistência, mas em qualquer camada que dependa de uma certa tecnologia e que precise passar a trabalhar de forma independente para que o software seja preservado..

Vamos começar analisando as camadas da solução:

Camadas

  • Domínio: camada responsável por armazenar as
    classes que representam o domínio do problema. A camada de domínio é o
    coração do projeto. É altamente recomendável que ela seja isolada do
    restante do projeto.
  • Infra: responsável por fornecer os recursos comuns a todas as camadas do projeto. Através de injeção de dependência,
    essa camada pode fornecer qualquer tecnologia necessária para problemas
    pontuais, como por exemplo: Envio de e-mail, Cliente do Twitter, etc.
  • Persistência: camada responsável por persistir os
    objetos criados no sistema. Essa camada pode ser construída com
    qualquer tecnologia de acesso a dados, desde ADO.NET puro até um ORM
    como EF4 ou NHibernate.

Com as responsabilidades definidas para cada uma das camadas, podemos iniciar a construção da camada de domínio.

Domínio

Como nosso foco não é resolver um domínio complexo e sim provar o
conceito, vamos utilizar um domínio simples, representado somente pela
classe “Link” e pelo seu repositório. Seguem os códigos das classes do
domínio:

namespace PersistenciaPlugavel.Dominio
{
public class Link
{
public virtual int Id { get; set; }
 
public virtual string Titulo { get; set; }
 
public virtual string Url { get; set; }
}
}
namespace PersistenciaPlugavel.Dominio
{
public interface IRepositorioLinks
{
void Adicionar(Link link);
 
Link ObterPorId(int id);
}
}

Para criar a independência da implementação concreta de um ORM,
precisamos trabalhar em um nível mais alto. Ainda no domínio, vamos
definir o conceito de Unidade de Trabalho, e implementá-lo de acordo
com cada ORM (assim como os repositórios).

using System;
 
namespace PersistenciaPlugavel.Dominio
{
public interface IUnidadeTrabalho : IDisposable
{
void Commit();
}
}

O “golpe do século” dessa solução está na simples abstração dos
conceitos de Unidade de Trabalho e Repositório. Notem que o domínio não
conhece suas implementações concretas, mas sim seus contratos
(interfaces). Quando precisamos das implementações concretas, pedimos
à camada de Infra.

Para saber mais sobre os Patterns Unit Of Work e Repository, veja as descrições no site do Martin Fowler:

Com o domínio definido e implementado, vamos partir para a construção do banco e das camadas de persistência.

O Banco de dados

Vamos utilizar apenas uma tabela para persistir as instâncias da classe Link. No exemplo, utilizei o SQL Server Express.

Camada de persistência com Entity Framework 4

Agora que já temos nosso domínio montado com as abstrações
necessárias, vamos construir as implementações das interfaces Unidade
de Trabalho e Repositório. Nossa camada de persistência conhece camada
de domínio, pois precisa das mesmas para criar os mapeamentos e
para conhecer os contratos (interfaces) que precisar cumprir.

A implementação da Unidade de Trabalho consiste em manipular o
principal objeto no EF, o Contexto. A solução é fazer com que a Unidade
de Trabalho possua um contexto e consiga repassá-lo para as
implementações dos repositórios.

using PersistenciaPlugavel.Dominio;
 
namespace PersistenciaPlugavel.PersistenciaEF4
{
public class UnidadeTrabalho : IUnidadeTrabalho
{
public Contexto Contexto { get; set; }
 
public UnidadeTrabalho()
{
this.Contexto = new Contexto();
}
 
public void Commit()
{
this.Contexto.SaveChanges();
}
 
public void Dispose()
{
this.Contexto.Dispose();
}
}
}

Como o repositório depende da Unidade de Trabalho, ele terá
acesso (através de um cast) ao Contexto e, assim, poderá usufruir de
todos os recursos dele.

using System.Linq;
using PersistenciaPlugavel.Dominio;
 
namespace PersistenciaPlugavel.PersistenciaEF4
{
public class RepositorioLinks : IRepositorioLinks
{
public IUnidadeTrabalho UnidadeTrabalho { get; set; }
 
public Contexto Contexto
{
get
{
return ((UnidadeTrabalho)this.UnidadeTrabalho).Contexto;
}
}
 
public RepositorioLinks(IUnidadeTrabalho unidadeTrabalho)
{
this.UnidadeTrabalho = unidadeTrabalho;
}
 
public void Adicionar(Link link)
{
this.Contexto.Links.AddObject(link);
}
 
public Link ObterPorId(int id)
{
return this.Contexto.Links.FirstOrDefault(l => l.Id == id);
}
}
}

A criação do Contexto não foi abordada, pois não foge do padrão de
trabalho com classes POCO. De qualquer forma, é possível fazer download
do código-fonte desse projeto ao final do artigo.

Agora que já possuímos o domínio e a persistência com base no Entity
Framework, vamos montar nossa camada de Infra e um código de exemplo no
qual poderemos ver a integração dos componentes acontecer sem acoplar o
código de negócio à tecnologia utilizada.

A camada Infra

O primeiro passo é construir, ainda na camada de persistência do EF4, um módulo do Ninject
que irá mapear para nossa camada de Infra qual classe irá atender a
determinada interface (ou que fornecedor irá atender a determinado
contrato).

using PersistenciaPlugavel.Dominio;
using Ninject.Modules;
 
namespace PersistenciaPlugavel.PersistenciaEF4
{
public class ModuloEF4 : NinjectModule
{
public override void Load()
{
base.Bind<IUnidadeTrabalho>().To<UnidadeTrabalho>();
base.Bind<IRepositorioLinks>().To<RepositorioLinks>();
}
}
}

Com o módulo definido, precisamos de uma classe que seja responsável
por resolver nossas interfaces. Essa classe irá utilizar o Ninject
internamente para isso, mas o acesso será somente através dos nossos
métodos.

using Ninject;
using Ninject.Parameters;
// Só é necessária a referência para o ORM que estivermos utilizando.
using PersistenciaPlugavel.PersistenciaEF4;
 
namespace PersistenciaPlugavel.Infra
{
public class Fabrica
{
private static Fabrica fInstancia;
 
public static Fabrica Instancia
{
get
{
if (fInstancia == null)
fInstancia = new Fabrica();
 
return fInstancia;
}
}
 
private StandardKernel Kernel { get; set; }
 
public Fabrica()
{
// Nesse ponto definimos qual módulo de persistência
//será utilizado.
var persistencia = new ModuloEF4();
 
this.Kernel = new StandardKernel(persistencia);
}
 
public T Obter<T>()
{
return this.Kernel.Get<T>();
}
 
public T ObterRepositorio<T>(object unidadeTrabalho)
{
return this.Kernel.Get<T>(new ConstructorArgument("unidadeTrabalho", unidadeTrabalho));
}
}
}

Com a nossa Fábrica pronta, podemos construir um código de exemplo
para a junção de tudo que foi construído até agora. Utilizamos um projeto
de teste para demonstrar a utilização do código:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using PersistenciaPlugavel.Dominio;
using PersistenciaPlugavel.Infra;
 
namespace PersistenciaPlugavel.Testes
{
[TestClass]
public class TestesPersistencia
{
[TestMethod]
public void Deve_inserir_e_obter_link()
{
var link = new Link
{
Titulo = "Heróis da T.I. - Por Denis Ferrari",
Url = "http://www.heroisdati.com/"
};
 
using (var uow = Fabrica.Instancia.Obter<IUnidadeTrabalho>())
{
var repositorio = Fabrica.Instancia.ObterRepositorio<IRepositorioLinks>(uow);
 
repositorio.Adicionar(link);
 
uow.Commit();
}
 
using (var uow = Fabrica.Instancia.Obter<IUnidadeTrabalho>())
{
var repositorio = Fabrica.Instancia.ObterRepositorio<IRepositorioLinks>(uow);
 
var linkPersistido = repositorio.ObterPorId(link.Id);
 
Assert.IsNotNull(linkPersistido);
}
}
}
}

Como podemos ver, não usamos diretamente nenhuma classe da camada de
persistência. A responsabilidade de obter as instâncias necessárias é
da nossa Fábrica, ou seja, abstraímos as implementações concretas e
trabalhamos somente com os membros declarados nas interfaces.

E como ficaria esse mesmo código com NHibernate?

O NHibernate

Para substituir o EF4 pelo NHibernate, precisamos apenas construir
as implementações das interfaces IUnidadeTrabalho e IRepositorioLinks.
Vejamos:

using PersistenciaPlugavel.Dominio;
using NHibernate;
 
namespace PersistenciaPlugavel.PersistenciaNHibernate
{
public class UnidadeTrabalho : IUnidadeTrabalho
{
public ISession Sessao { get; set; }
 
public UnidadeTrabalho()
{
this.Sessao = SessionFactory.Instancia.ObterSessao();
 
this.Sessao.FlushMode = FlushMode.Commit;
this.Sessao.BeginTransaction();
}
 
public void Commit()
{
this.Sessao.Transaction.Commit();
}
 
public void Dispose()
{
this.Sessao.Close();
this.Sessao.Dispose();
}
}
}

A unidade de trabalho do NHibernate em vez de manipular o
Contexto irá manipular a Sessão. A lógica de construção do repositório
também é muito semelhante.

using PersistenciaPlugavel.Dominio;
using NHibernate;
 
namespace PersistenciaPlugavel.PersistenciaNHibernate
{
public class RepositorioLinks : IRepositorioLinks
{
public IUnidadeTrabalho UnidadeTrabalho { get; set; }
 
public ISession Sessao
{
get
{
return ((UnidadeTrabalho)this.UnidadeTrabalho).Sessao;
}
}
 
public RepositorioLinks(IUnidadeTrabalho unidadeTrabalho)
{
this.UnidadeTrabalho = unidadeTrabalho;
}
 
public void Adicionar(Link link)
{
this.Sessao.SaveOrUpdate(link);
}
 
public Link ObterPorId(int id)
{
return this.Sessao.Get<Link>(id);
}
}
}

Com as duas classes principais construídas, precisamos informar à
fábrica que ela irá trabalhar com o NHibernate, e não com o EF4.
Para isso, vamos construir mais um módulo do Ninject e alterar apenas
uma linha de código na Fábrica.

using Ninject.Modules;
using PersistenciaPlugavel.Dominio;
 
namespace PersistenciaPlugavel.PersistenciaNHibernate
{
public class ModuloNHibernate : NinjectModule
{
public override void Load()
{
base.Bind<IUnidadeTrabalho>().To<UnidadeTrabalho>();
base.Bind<IRepositorioLinks>().To<RepositorioLinks>();
}
}
}
...
public Fabrica()
{
// Nesse ponto definimos qual módulo de persistência
//será utilizado.
var persistencia = new ModuloNHibernate();
//var persistencia = new ModuloEF4();
 
this.Kernel = new StandardKernel(persistencia);
}
...

Quando rodarmos o nosso código de teste, vamos perceber que as
classes do NHibernate estão sendo utilizadas, e não as classes do EF4.

Como vimos, manter um certo nível de abstração (nem tanto e nem tão
pouco) facilita muito nos momentos em que o software sofre uma
alteração. Este artigo não mostra uma alteração que vemos todos os
dias, mas mostra que qualquer alteração (inclusive a demostrada aqui)
pode ser muito mais suave, se durante a construção abstrairmos os
elementos certos.

Download do projetoClique aqui

Abraços!