Vers le référentiel ou pas vers le référentiel

10

Lorsque j'ai découvert la conception pilotée par domaine pour la première fois, j'ai également découvert le référentiel et les modèles d'unité de travail qui semblaient autrefois de premier ordre pour les enfants sympas qui lançaient des requêtes SQL comme des hommes des cavernes contre des bases de données. Plus je me suis plongé dans ce sujet, plus j'ai appris qu'ils ne semblent plus nécessaires à cause des ORM comme EF et NHibernate qui implémentent à la fois l'unité de travail et les référentiels dans une API, appelée session ou contexte.

Maintenant, je ne sais pas quoi faire. Vers le référentiel ou non vers le référentiel. Je comprends vraiment l'argument selon lequel de telles abstractions qui fuient ne font que compliquer les choses tout en n'ajoutant absolument rien qui puisse simplifier l'accès aux données, cependant, il ne semble pas juste de coupler tous les aspects possibles de mon application, par exemple à Entity Framework . Habituellement, je respecte quelques directives simples:

  1. La couche domaine est le cœur du système, contenant des entités, des services, des référentiels ...
  2. La couche infrastructure fournit des implémentations d'interfaces de domaine présentant un problème d'infrastructure, par exemple fichier, base de données, protocoles ..
  3. La couche d'application héberge une racine de composition qui connecte les éléments et orchestre tout.

Mes solutions ressemblent généralement à ceci:

Domain.Module1
Domain.Module2
    IModule2Repo
    IModule2Service
    Module2
Infrastructure.Persistence
    Repositories
        EntityFrameworkRepositoryBase
MyApp
    Boostrapper
        -> inject EntityFrameworkRepositoryBase into IRepository etc.

Je garde ma couche de domaine propre en utilisant un IRepository<'T>qui est également une préoccupation de domaine ne dépendant de rien d'autre qui me dit comment accéder aux données. Quand je ferais maintenant une implémentation concrète IModule2Servicequi nécessite un accès aux données, je devrais injecter DbContextet par là, le coupler directement à la couche infrastructure. ( En ce qui concerne le projet Visual Studio, cela peut devenir très délicat en raison des dépendances circulaires! )

De plus, quelle peut être une alternative aux dépositaires et aux fucktons d'œuvres ? CQRS? Comment résumer un cadre infrastructurel pur?

Acrotygme
la source
1
Soit dit en passant, si vous parlez de «référentiel» comme il est décrit dans DDD, EF et nHibernate ne sont pas des référentiels. Bien sûr, ils résument partiellement le mécanisme d'accès aux données, mais il y a bien plus dans un référentiel que cela .
Eric King

Réponses:

4

L'ingénierie est une question de compromis. Il en va de même pour le développement de logiciels. À l'heure actuelle, je crois que la seule autre option, qui serait plus simple, est de travailler directement avec ORM. Mais comme vous l'avez dit, cela pourrait vous enfermer dans un cadre de persistance spécifique.

Il faut donc se demander "La complexité supplémentaire vaut-elle le découplage de votre code de la persistance". Chaque fois que j'entends des gens dire qu'ils veulent dissocier leur code de la persistance, je demande "Combien de fois dans votre carrière avez-vous changé votre cadre de persistance?"

Le problème que je vois avec les référentiels est qu'ils rendent les changements communs plus difficiles. Par exemple. ajout d'une nouvelle façon d'interroger un agrégat. Et ils facilitent (soi-disant) des changements inhabituels. Par exemple. modification de l'implémentation des référentiels.

Ensuite, il y a aussi l'argument des tests unitaires, auquel je dis: «Si le cadre de persistance ne vous permet pas de se moquer d'une base de données, en mémoire ou localement, alors cela ne vaut pas la peine du tout d'utiliser le cadre.

Euphorique
la source
1
Le but du référentiel est de dissocier l'application de la persistance et aucune complexité n'est impliquée. Et je modifie les détails de persistance au moins une fois, car l'accès à la base de données est la dernière chose que je code, jusqu'à ce que j'utilise dans les dépôts de mémoire. En outre, il existe un piège très subtil lorsque vous utilisez directement un détail de persistance. Vos objets métier auront tendance à être conçus pour être compatibles avec lui, au lieu d'être agnostiques. Et c'est parce qu'une entité riche et correctement encapsulée ne peut pas être directement restaurée à partir de n'importe quel stockage (sauf la mémoire) sans (quelques moches) solutions de contournement
MikeSW
À propos de l'ajout d'une nouvelle façon d'interroger une racine agrégée ou n'importe quelle entité, c'est pourquoi CQRS est apparu. Personnellement, je conserve le référentiel à des fins de domaine et j'utilise des gestionnaires de requêtes pour les requêtes réelles. Et ces gestionnaires sont très étroitement couplés à la base de données et sont très efficaces dans ce qu'ils font.
MikeSW
3

Je suis pro-référentiel, même si je me suis éloigné des modèles génériques de référentiel. Au lieu de cela, j'aligne mes référentiels sur la fonction métier qu'ils servent. Les référentiels n'ont pas pour but d'abstraire l'ORM, car je ne m'attends pas à ce que cela change, et en même temps, j'évite de rendre un référentiel trop granulaire. (Ie CRUD) Au lieu de cela, mes référentiels ont deux ou trois objectifs clés:

  • Récupération de données
  • Création de données
  • Suppression définitive

Pour la récupération des données, le référentiel revient toujours IQueryable<TEntity>. Pour la création de données, il renvoie TEntity. Le référentiel gère mon filtrage de niveau de base tel que l'état "actif" de l'autorisation pour les systèmes qui utilisent des modèles de suppression logicielle et l'état "actuel" pour les systèmes qui utilisent des données historiques. La création de données est uniquement chargée de s'assurer que les références requises sont résolues et associées et que l'entité est configurée et prête à fonctionner.

La mise à jour des données est de la responsabilité de la logique métier travaillant avec les entités en question. Cela peut inclure des choses comme les règles de validation. Je n'essaie pas d'encapsuler cela dans une méthode de référentiel.

La suppression dans la plupart de mes systèmes est une suppression logicielle afin qu'elle relève de la mise à jour des données. (IsActive = false) Dans le cas de suppressions matérielles, ce serait une ligne unique dans le référentiel.

Pourquoi les référentiels? Test-capacité. Bien sûr, DbContext peut être moqué, mais il est plus simple de se moquer d'une classe qui retourneIQueryable<TEntity>. Ils jouent également bien avec le modèle UoW, personnellement, j'utilise le modèle DbContextScope de Mehdime pour étendre l'unité de travail au niveau que je veux (c'est-à-dire les contrôleurs dans MVC) et laisser mes contrôleurs et les classes de service d'assistance utiliser les référentiels sans avoir besoin de passer des références à le UoW / dbContext autour. L'utilisation d'IQueryable signifie que vous n'avez pas besoin de beaucoup de méthodes d'encapsulation dans le référentiel, et votre code peut optimiser la façon dont les données vont être consommées. Par exemple, le référentiel n'a pas besoin d'exposer des méthodes telles que "Exists" ou "Count", ou d'essayer d'encapsuler des entités avec d'autres POCO dans les cas où vous souhaitez des sous-ensembles de données. Ils n'ont même pas besoin de gérer les options de chargement rapide pour les données connexes dont vous pouvez ou non avoir besoin. En passant IQueryable, le code appelant peut:

.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()

Très flexible, et à partir d'un PoV de test, mon référentiel simulé doit simplement renvoyer des listes d'objets d'entité remplis.

En ce qui concerne les référentiels génériques, je m'en suis éloigné pour la plupart parce que lorsque vous vous retrouvez avec un référentiel par table, vos contrôleurs / services se retrouvent avec des références à plusieurs référentiels pour effectuer une action commerciale. Dans la plupart des cas, seulement un ou deux de ces référentiels effectuent réellement des opérations d'écriture (à condition que vous utilisiez correctement les propriétés de navigation) tandis que les autres prennent en charge ceux avec des lectures. Je préfère avoir quelque chose comme un référentiel de commandes qui est capable de lire et de créer des commandes, et de lire toutes les recherches pertinentes, etc. (objets clients légers, produits, etc.) pour référence lors de la création d'une commande, plutôt que de toucher 5 ou 6 référentiels différents. Cela peut violer les puristes DNRY, mais mon argument est que le but du référentiel est de servir à la création d'Ordres qui inclut les références associées.Repository<Product>pour obtenir des produits où pour la base d'une commande j'ai seulement besoin d'une entité avec une poignée de champs. Mon OrderRepository pourrait avoir une .GetProducts()méthode de retour IQueryable<ProductSummary>que je trouve plus agréable qu'une Repository<Product>qui finit par avoir plusieurs méthodes "Get" pour essayer de répondre à différents domaines des besoins de l'application et / ou une expression de filtrage de passage complexe.

J'opte pour un code plus simple, facile à suivre, à tester et à régler. Il peut être abusé de mauvaises manières, mais je préfère avoir quelque chose où les abus sont faciles à repérer et à corriger que d'essayer de "verrouiller" le code d'une manière qui ne peut pas être abusée, échouer, puis avoir quelque chose qui est un cauchemar pour arriver à faire ce que le client paie pour le faire à la fin. :)

Steve Py
la source