Dans cet article, nous examinerons et approfondirons la pertinence de l’utilisation simultanée des patrons de conception Specification et Repository dans le cadre générique d’un projet exploitant la plateforme de développement .Net Core. Bien qu’il n’existe pas de solution miracle en matière de structuration d’application, l’adoption de cette approche offre une modularité et une réutilisabilité significative au code.

Avant d’aborder le sujet principal, il est essentiel de définir quelques termes pour une contextualisation appropriée. Généralement, le patron Repository est associé à celui de Unit Of Work pour établir une couche d’abstraction entre la logique métier et l’accès aux données d’une solution applicative. En substance, le premier assure la médiation entre les données et le domaine, tandis que l’autre coordonne les transactions d’écriture et résout les problèmes de concurrence.

Quant au modèle de spécification, une définition pertinente trouvée sur Wikipédia le décrit comme un modèle de conception logicielle permettant la recomposition des règles métiers par l’enchaînement logique booléen.

Quel est l’avantage intrinsèque de cette approche ? Je tenterai de répondre à cette question à travers une démonstration pratique (Proof Of Concept), conformément à la démarche habituelle d’un développeur.

Pour débuter, notre solution sera développée sous le Framework .NET 7.0, baptisée MyBlog.Poc, avec le choix du modèle Console App. Un projet de bibliothèque de classes, nommé MyBlog.Poc.Core, sera ajouté à la solution et référencé dans l’application console, représentant le cœur de notre démonstration.

Les entités modèles sont définis comme ci-dessous :

Représentation UML des entités modèles.

 

En ce qui concerne la création du Contexte via Entity Framework Core et la mise en place de la base de données, j’ai mis à disposition un référentiel sur GitHub . Vous avez ainsi la possibilité de le forker et de le tester par vous-même.

Une fois la configuration initiale accomplie, nous pouvons orienter notre attention vers le sujet principal et sa mise en œuvre. Pour commencer, créons une interface générique au sein de la couche Core afin de définir diverses règles pour la récupération de données :

 

Afin de tirer parti des éléments que nous avons mis en place, nous allons créer une classe générique abstraite que nous désignerons sous le nom de BaseQuerySpecification. Cette classe sera la mère à toutes celles qui en découleront.

 

T représente un type générique qui hérite de BaseEntity qui est la classe mère de toutes nos entités modèles, restreignant ainsi l’utilisation de BaseQuerySpecification.

Avant d’aborder la gestion de nos règles métiers, nous allons incorporer le patron de conception Repository à la solution. Pour ce faire, nous allons commencer par créer une interface IBaseRepository définissant les différentes méthodes permettant d’abstraire la couche d’accès aux données. Celle-ci prendra en paramètre en un type générique enfant de DbContext (Entity Framework Core).

Il est à noter que toutes les méthodes de lecture prendront en argument une instance qui implémente l’interface ISpecification. Nous allons orienter notre attention particulièrement sur celles-ci.

Poursuivons en créant la classe générique BaseRepository qui va permettre leur implémentation :

Le type générique CTX hérite de DbContext et on le récupère au moment de l’instanciation grâce à l’Inversion of Control (IoC).

Maintenant qu’il est implémenté, nous devons indiquer à notre solution comment l’injecter (DI), afin de pouvoir l’utiliser du côté console :

Maintenant que tous les éléments sont en place, imaginons le scénario d’un développeur chargé de créer une page affichant une liste d’articles de blog. Dans les spécifications du récit utilisateur (User Story), il est indiqué que l’on souhaite afficher des informations sur les auteurs de ces articles. Pour réaliser cela, nous allons créer la classe ArticleQuerySpecification. Elle comprendra un constructeur qui fait référence à celui de la classe parente, en lui donnant en argument l’expression Criteria :

 

A présent que nous l’avons défini pour exprimer nos besoins, nous pouvons essayer d’afficher nos articles.

On peut observer que cela fonctionne. Cependant, il devient rapidement évident qu’en termes de performances, l’approche n’est pas optimale. En effet, si la base de données contient des milliers d’articles, cela pourrait entraîner des problèmes.

Ajoutons la pagination pour paramétrer le chargement, par exemple, de 3 articles par page :

Grâce aux résultats obtenus on comprend assez facilement que les possibilités d’usage sont multiples.

Partons d’un autre scénario typique, où nous devons créer la page de détails d’un article. Nous devons afficher son contenu, son auteur, les commentaires associés, ainsi que les auteurs de ceux-ci.

Commençons par intégrer à la classe ArticleQuerySpecification les éléments que nous désirons récupérer avec notre article. Pour ce faire, ajoutons un nouveau constructeur qui accepte en paramètre l’Id d’un article, et n’oublions pas d’appeler le constructeur du parent en lui fournissant le critère de récupération en argument.

Maintenant que cela est mis en œuvre, nous pouvons invoquer la méthode FindByAsync de notre repository pour finaliser cette démarche. En examinant de près nos deux méthodes, il apparaît que nous pouvons les factoriser.

 

Créons une classe que nous nommerons SpecificationEvaluator, chargée de centraliser toute notre logique.

Maintenant que nous l’avons correctement créée, nous pouvons nous rendre dans notre repository pour l’appliquer.

Je n’invente rien, je m’applique simplement à suivre le principe DRY (Don’t Repeat Yourself) afin de rendre mon code plus propre et plus maintenable.

À présent que notre repository est prêt à être utilisé et qu’il ne contient plus de duplication de code, nous pouvons passer à la mise en place de la partie détaillée d’un article, côté console.

 

En exécutant, nous allons déclencher une exception bien connue des développeurs : NullReferenceException.

Cette erreur survient car nous tentons de lire les auteurs des commentaires alors que nous ne les avons pas inclus dans l’état (state) de notre contexte. Nous avons uniquement récupéré l’auteur de notre article 3, mais pas ceux de ses commentaires.

Pour résoudre cette problématique, nous allons créer une nouvelle classe CommentaryQuerySpecification qui nous permettra de remédier à cette situation.

 

Maintenant que cela est en place, nous obtenons le résultat attendu avec des spécifications adaptées à nos cas d’utilisation. Nous avons récupéré de la base de données uniquement les données spécifiques ceux-ci.

 

En conclusion, nous avons examiné les avantages que le patron de conception Specification apporte lorsqu’il est associé au Repository. Il offre la possibilité d’encapsuler la logique des requêtes courantes et leur réutilisabilité, permettant ainsi à d’autres classes de respecter le principe de responsabilité unique. Cette approche permet de prévenir l’expansion des méthodes personnalisées d’accès aux données coté repository, maintenant ainsi notre code organisé et facilitant l’ajout de complexités à mesure que l’application se développe.

Il est important de noter que le design pattern Unit of Work n’a pas été inclus dans le cadre de ce sujet principal, mais il peut être intégré selon les besoins spécifiques de vos développements applicatifs, offrant une perspective intéressante pour la gestion des transactions et la résolution des problèmes de concurrence.

 

 

 

 

 

Ne ratez plus aucune actualité avec la newsletter mensuelle de SoftFluent

Newsletter SoftFluent