Création d'une couche d'abstraction sur la couche ORM

12

Je pense que si vos référentiels utilisent un ORM, il est déjà suffisamment extrait de la base de données.

Cependant, là où je travaille maintenant, quelqu'un pense que nous devrions avoir une couche qui résume l'ORM au cas où nous souhaiterions changer l'ORM plus tard.

Est-ce vraiment nécessaire ou c'est simplement beaucoup de travail pour créer une couche qui fonctionnera sur de nombreux ORM?

Éditer

Juste pour donner plus de détails:

  1. Nous avons des classes POCO et Entity Class qui sont mappées avec AutoMapper. La classe d'entité est utilisée par la couche Repository. La couche de référentiel utilise ensuite la couche d'abstraction supplémentaire pour communiquer avec Entity Framework.
  2. La couche métier n'a en aucun cas un accès direct à Entity Framework. Même sans la couche d'abstraction supplémentaire sur l'ORM, celle-ci doit utiliser la couche de service qui utilise la couche de référentiel. Dans les deux cas, la couche métier est totalement séparée de l'ORM.
  3. L'argument principal est de pouvoir changer ORM à l'avenir. Puisqu'il est vraiment localisé à l'intérieur de la couche Repository, pour moi, il est déjà bien séparé et je ne vois pas pourquoi une couche d'abstraction supplémentaire est nécessaire pour avoir un code "de qualité".
Patrick Desjardins
la source
3
la couche supplémentaire doit être justifiée, sinon elle viole YAGNI. En d'autres termes, quelqu'un croyant que vous en avez besoin, a le fardeau de prouver que
moucher
2
Je peux comprendre vouloir une couche de domaine qui expose seulement un sous-ensemble d'opérations voulu - les ORM ont tendance à être une surface un peu trop large (disons que vous ne voulez pas autoriser les mises à jour d'une entité non dirigée par une autre entité contenant). Avoir une telle couche d'abstraction aide à cela.
Odé
4
Vous aurez probablement besoin d'une deuxième couche d'abstraction pour la première couche d'abstraction au-dessus de l'ORM au cas où vous voudriez également changer la première couche.
David Peterman
1
@David Pendant que nous ajoutons de la redondance, changez tous vos if (boolean) en if (boolean == true) et si vous souhaitez régurgiter davantage, if (boolean == true == true ...) et ainsi de suite
brian
1
Article
RMalke

Réponses:

12

De cette façon réside la folie. Il est très peu probable que vous ayez besoin de changer d'ORM. Et si jamais vous décidez de changer l'ORM, le coût de réécriture des mappages sera une infime fraction du coût de développement et de maintenance de votre propre méta-ORM. Je m'attendrais à ce que vous puissiez écrire quelques scripts pour faire 95% du travail nécessaire pour changer d'ORM.

Les cadres internes sont presque toujours une catastrophe. En construire un en prévision des besoins futurs est presque une catastrophe garantie. Les cadres réussis sont extraits de projets réussis, pas construits à l'avance pour répondre à des besoins imaginaires.

Kevin Cline
la source
12

L'ORM fournit une abstraction pour que votre couche de données soit indépendante de son SGBDR, mais cela peut ne pas être suffisant pour «délier» votre couche métier de votre couche de données. Plus précisément, vous ne devez pas laisser les objets mappés aux tables SGBDR se «répandre» directement dans la couche de gestion.

À tout le moins, votre couche métier doit programmer des interfaces que vos objets mappés par table et gérés par ORM de la couche de données pourraient potentiellement implémenter. En outre, vous devrez peut-être créer une couche d'interface de création de requête abstraite pour masquer les capacités de requête natives de votre ORM. Le but principal est d'éviter de "faire entrer" un ORM particulier dans votre solution au-delà de sa couche de données. Par exemple, il peut être tentant de créer des chaînes HQL ( Hibernate Query Language ) dans la couche métier. Cependant, cette décision apparemment innocente lierait votre couche d'entreprise à Hibernate, clouant ainsi l'entreprise et les couches d'accès aux données ensemble; vous devriez essayer d'éviter cette situation autant que possible.

EDIT: Dans votre cas, la couche supplémentaire à l'intérieur du référentiel est une perte de temps: sur la base de votre point numéro deux, votre couche métier est suffisamment isolée de votre référentiel. Fournir une isolation supplémentaire introduirait une complexité inutile, sans avantages supplémentaires.

Le problème avec la construction d'une couche d'abstraction supplémentaire à l'intérieur de votre référentiel est que la "marque" particulière d'ORM dicte la façon dont vous interagissez avec. Si vous créez un wrapper mince qui ressemble à votre ORM, mais sous votre contrôle, le remplacement de l'ORM sous-jacent sera à peu près aussi difficile qu'il le serait sans cette couche supplémentaire. Si, en revanche, vous créez une couche qui ne ressemble en rien à votre ORM, vous devez alors remettre en question votre choix de la technologie de mappage relationnel-objet.

dasblinkenlight
la source
Je suis tellement heureux que .NET ait résolu ce problème en créant l'objet de requête dans la plate-forme. Le port .NET d'Hibernate le prend même en charge.
Michael Brown
2
@MikeBrown Oui, et .NET a également fourni deux technologies ORM concurrentes qui lui sont propres, toutes deux utilisant la technologie LINQ!
dasblinkenlight
@dasblinkenlight J'ai mis à jour la question pour vous donner des informations supplémentaires.
Patrick Desjardins
Dernièrement, j'ai adopté l'approche consistant à faire dépendre la couche métier d'une couche de données basée sur des interfaces de type carte et de type ensemble pour stocker l'état. Je crois maintenant que ces interfaces représentent assez bien le souci de la conservation de l'état (inspiré par le style fonctionnel de programmation) et fournissent une belle séparation de l'ORM de choix via une implémentation plutôt mince.
beluchin
2

UnitOfWork fournit généralement cette abstraction. C'est un endroit qui doit changer, vos référentiels en dépendent via une interface. Si jamais vous avez besoin de changer O / RM, implémentez simplement une nouvelle UoW dessus. Un et terminé.

BTW, cela va au-delà de la simple commutation O / RM, pensez aux tests unitaires. J'ai trois implémentations UnitOfWork, une pour EF, une pour NH (parce que j'ai en fait dû changer de O / RM à mi-projet pour un client qui voulait le support d'Oracle), et une pour la persistance InMemory. La persistance InMemory était parfaite pour les tests unitaires ou même pour le prototypage rapide avant que je ne sois prêt à mettre une base de données derrière.

Le cadre est simple à mettre en œuvre. Vous avez d'abord votre interface générique IRepository

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);

}

Et l'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;
}

Vient ensuite le référentiel de base (votre choix s'il doit être abstrait ou non

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);
   }
}

Implémenter IUnitOfWork est un jeu d'enfant pour EF, NH et In Memory. La raison pour laquelle je retourne IQueryable, c'est pour la même raison, a indiqué Ayende dans son article, le client peut filtrer, trier, grouper et même projeter le résultat à l'aide de LINQ et vous bénéficiez toujours de tout cela du côté serveur.

Michael Brown
la source
1
Mais la question ici est de déterminer si cette couche ci-dessus est utile et doit être le portier de tous les accès aux données.
brian
J'aimerais pouvoir pointer mon article de blog sur l'implémentation de l'unité de travail / du référentiel. Il discute des préoccupations exactes du PO.
Michael Brown
Donner un nom à la couche ne signifie pas qu'elle est nécessaire ou utile.
kevin cline
Remarque selon l'OP, il a une cartographie supplémentaire entre l'accès aux données et la couche métier. Pour moi, mes objets métier et objets entité sont les mêmes. EF et NH fournissent des API de cartographie incroyables telles que la cartographie des données devient rarement (voire jamais) une préoccupation.
Michael Brown
Comment traduire une expression arbitraire en un appel ORM efficace? Vous ne pouvez pas simplement tout récupérer et jeter les lignes qui ne correspondent pas au filtre.
kevin cline