Criando uma camada de abstração sobre a camada ORM

12

Acredito que, se você tiver seus repositórios, use um ORM que já é suficiente abstraído do banco de dados.

No entanto, onde estou trabalhando agora, alguém acredita que devemos ter uma camada que abstraia o ORM, caso desejemos alterá-lo mais tarde.

É realmente necessário ou é simplesmente muito caro criar uma camada que funcione em muitos ORM?

Editar

Só para dar mais detalhes:

  1. Temos a classe POCO e a Entity Class que são mapeadas com o AutoMapper. A classe de entidade é usada pela camada Repository. A camada de repositório usa a camada adicional de abstração para se comunicar com o Entity Framework.
  2. A camada de negócios não tem acesso direto ao Entity Framework. Mesmo sem a camada adicional de abstração no ORM, esta precisa usar a camada de serviço que utiliza a camada de repositório. Nos dois casos, a camada de negócios é totalmente separada do ORM.
  3. O argumento principal é poder alterar o ORM no futuro. Como ele está realmente localizado dentro da camada Repository, para mim, já está bem separado e não vejo por que uma camada adicional de abstração é necessária para ter um código de "qualidade".
Patrick Desjardins
fonte
3
camada extra precisa de justificativa, caso contrário, viola o YAGNI. Em outras palavras, alguém que acredita que você precisa, tem um ônus para provar isso
gnat
2
Eu posso entender o desejo de uma camada de domínio que exponha apenas um subconjunto de operações desejado - os ORMs tendem a ser uma área de superfície um pouco grande demais (digamos que você não deseja permitir atualizações para uma entidade não direcionada por outra entidade que contém). Ter essa camada de abstração ajuda nisso.
Oded
4
Você provavelmente precisará de uma segunda camada de abstração para a primeira camada de abstração acima do ORM, caso deseje alterar a primeira camada também.
David Peterman
1
@ David Enquanto estivermos adicionando redudância, altere todo o seu if (booleano) para if (booleano == true) e se você quiser regurgitar mais do mesmo, if (booleano == true == true ...) e assim por diante
brian
1
Publicação
RMalke

Respostas:

12

Dessa maneira está a loucura. É altamente improvável que você precise alterar os ORMs. E se você decidir alterar o ORM, o custo de reescrever os mapeamentos será uma pequena fração do custo para desenvolver e manter seu próprio meta-ORM. Eu esperava que você pudesse escrever alguns scripts para fazer 95% do trabalho necessário para alternar ORMs.

As estruturas internas são quase sempre um desastre. Construir um em antecipação às necessidades futuras é quase um desastre garantido. Estruturas bem-sucedidas são extraídas de projetos bem-sucedidos, não construídas com antecedência para atender às necessidades imaginárias.

Kevin Cline
fonte
12

O ORM fornece uma abstração para que sua camada de dados seja independente de seu RDBMS, mas pode não ser suficiente para "desatar" a camada de negócios da camada de dados. Especificamente, você não deve permitir que objetos mapeados para tabelas RDBMS "vazem" diretamente na camada de negócios.

No mínimo, sua camada de negócios precisa programar para interfaces que seus objetos mapeados por tabela e gerenciados por ORM da camada de dados poderiam potencialmente implementar. Além disso, pode ser necessário criar uma camada baseada em interface da construção de consultas abstratas para ocultar os recursos de consulta nativos do seu ORM. O objetivo principal é evitar "inserir" qualquer ORM específico em sua solução além da camada de dados. Por exemplo, pode ser tentador criar seqüências de caracteres HQL ( Hibernate Query Language ) na camada de negócios. No entanto, essa decisão aparentemente inocente vincularia sua camada de negócios ao Hibernate, unindo as camadas de negócios e de acesso a dados; você deve tentar evitar essa situação o máximo possível.

EDIT: No seu caso, a camada adicional dentro do repositório é uma perda de tempo: com base no seu ponto número dois, sua camada de negócios é suficientemente isolada do seu repositório. O fornecimento de isolamento adicional introduziria complexidade desnecessária, sem benefícios adicionais.

O problema com a criação de uma camada extra de abstração dentro do seu repositório é que a "marca" específica do ORM determina a maneira como você interage com ele. Se você criar um invólucro fino que se pareça com o seu ORM, mas esteja sob seu controle, a substituição do ORM subjacente será mais ou menos tão difícil quanto seria sem essa camada adicional. Se, por outro lado, você criar uma camada que não se parece com o seu ORM, deverá questionar sua escolha da tecnologia de mapeamento objeto-relacional.

dasblinkenlight
fonte
Estou tão feliz que o .NET resolveu esse problema inserindo o Query Object na plataforma. A porta .NET do Hibernate ainda suporta.
Michael Brown
2
@MikeBrown Sim, e o .NET também forneceu duas tecnologias ORM concorrentes próprias, ambas usando a tecnologia LINQ!
dasblinkenlight
@dasblinkenlight Atualizei a pergunta para fornecer informações adicionais.
Patrick Desjardins
Ultimamente, adotei a abordagem de fazer com que a camada de negócios dependesse de uma camada de dados baseada em interfaces tipo mapa e tipo conjunto para armazenar o estado. Agora acredito que essas interfaces representam bastante a preocupação de manter o estado (inspirado no estilo funcional de programação) e fornecem uma boa separação do ORM de escolha por meio de uma implementação bastante fina.
22716 beluchin
2

O UnitOfWork geralmente fornece essa abstração. É um lugar que precisa mudar, seus repositórios dependem dele por meio de uma interface. Se você precisar alterar o O / RM, basta implementar uma nova UoW sobre ele. Um e pronto.

BTW vai além de apenas trocar de O / RM, pense em testes de unidade. Eu tenho três implementações UnitOfWork, uma para EF, uma para NH (porque eu realmente precisei alternar O / RMs no meio do projeto para um cliente que desejava suporte Oracle) e uma para persistência do InMemory. A persistência do InMemory foi perfeita para testes de unidade ou mesmo para prototipagem rápida antes que eu estivesse pronto para colocar um banco de dados por trás dele.

A estrutura é simples de implementar. Primeiro você tem sua interface IRepository genérica

public interface IRepository<T>
  where T:class
{
  IQueryable<T> FindBy(Expression<Func<T,Bool>>filter);
  IQueryable<T> GetAll();
  T FindSingle(Expression<Func<T,Bool>> filter);
  void Add(T item);
  void Remove(T item);

}

E a interface IUnitOfWork

public interface IUnitOfWork
{
   IQueryable<T> GetSet<T>();
   void Save();
   void Add<T>(T item) where T:class;
   void Remove<T>(T item) where T:class;
}

Em seguida, é o repositório base (sua escolha é se deve ou não ser abstrato

public abstract class RepositoryBase<T>:IRepository<T>
  where T:class
{
   protected readonly IUnitOfWork _uow;

   protected RepositoryBase(IUnitOfWork uow)
   { 
      _uow=uow;
   }

   public IQueryable<T> FindBy(Expression<Func<T,Bool>>filter)
   {
      return _uow.GetSet<T>().Where(filter);
   }

   public IQueryable<T> GetAll()
   {
      return _uow.GetSet<T>();
   }

   public T FindSingle(Expression<Func<T,Bool>> filter)
   {
      return _uow.GetSet<T>().SingleOrDefault(filter);
   }

   public void Add(T item)
   {
      return _uow.Add(item);
   }

   public void Remove(T item)
   {
      return _uow.Remove(item);
   }
}

Implementar o IUnitOfWork é uma brincadeira de criança para EF, NH e In Memory. O motivo pelo qual eu retornei o IQueryable é pelo mesmo motivo, Ayende mencionou em seu post, o cliente pode filtrar, classificar, agrupar e até mesmo projetar o resultado usando o LINQ e você ainda obtém o benefício de tudo ser feito no servidor.

Michael Brown
fonte
1
Mas a questão aqui é determinar se essa camada acima é útil ou não e deve ser o guardião de todos os acessos de dados.
brian
Gostaria de poder apontar para a minha postagem no blog sobre a implementação da Unidade de Trabalho / Repositório. Ele discute as preocupações exatas do OP.
Michael Brown
Dar um nome à camada não significa que seja necessário ou útil.
Kevin cline
Observe que, de acordo com o OP, ele possui um mapeamento extra entre o acesso a dados e a camada de negócios. Para mim, meus objetos de negócios e objetos de entidade são os mesmos. EF e NH fornecem APIs de mapeamento incríveis, de modo que o mapeamento de dados raramente (se é que alguma vez) se torna uma preocupação.
Michael Brown
Como você traduz uma expressão arbitrária em uma chamada ORM eficiente? Você não pode simplesmente buscar tudo e jogar fora as linhas que não correspondem ao filtro.
Kevin cline