[DUMD 13/24] Le design pattern Repository

Auteur du billet de blog : Nicolas Hilaire - Neotech Solutions

Nicolas Hilaire

Consultant .NET
  Publié le mardi 13 décembre 2016

Artisan logiciel particulièrement intéressé par les technologies .NET. Polyvalent et curieux, je suis néanmoins à l'écoute des autres technologies du marché. MVP (Microsoft Most Valuable Professional) de 2007 à 2014, je suis également auteur d'un ouvrage pour apprendre le C#, à destination des débutants et de plusieurs MOOCs sur le C#, Windows Phone ou ASP.NET MVC...

Treizième billet sur comment devenir un meilleur développeur. Pour retrouver le sommaire ainsi que tous les liens, rendez-vous sur le premier billet.

Je vais maintenant vous présenter le patron de conception Repository. On pourrait le traduire en français par "dépôt", mais je ne pense pas avoir vu quelqu'un le mentionner ainsi. De toutes façons, nous autre développeurs parlons tout le temps avec des sigles en anglais, donc pas la peine de vous embêter à apprendre une traduction. Oubliez le dépôt, nous allons explorer le design pattern Repository.

 

Le repository en action

Autant le dire tout de suite, le repository est fait pour abstraire l'accès aux données. Donc il n'a de sens que dans la récupération de données. Et peu importe d'où vient la donnée d'ailleurs ; que ça soit en base de données (super classique !), dans des fichiers (old school mais toujours utile !), à partir d'une API REST (ouais, ça c'est la mode, micro-services et tout ! ^^), etc. Bref, voici encore une solution pour rajouter de l'abstraction.

Imaginons que vous ayez besoin de récupérer les 10 produits les plus vendus sur votre site d'e-commerce. Avec un outil comme Entity Framework, qui est l'ORM de Microsoft, et à l'aide de Linq, vous pourriez écrire quelque chose du genre :

 

Et si plus loin vous avez besoin de récupérer des produits qui contiennent le mot "Pattern" dans leurs noms, vous allez faire :

 

 

Sauf que votre chef - le roi du changement - arrive et vous dit que suite au rachat par la maison mère, on doit utiliser un web service fourni par un partenaire où est complètement déléguée la gestion du catalogue. Et vous voilà à repasser sur tout le code pour enlever ce malheureux Entity Framework et le remplacer par autre chose, qui après-demain va peut-être encore changer...

Bref, vous l'aurez compris. Il vous faut une abstraction. Mais si ce n'était que ça, vous sauriez faire maintenant que vous êtes rodés aux design patterns !

Le repository va un peu plus loin : il vous incite à faire en sorte que cette abstraction vous permette d'interroger vos produits comme une espèce de collection en mémoire. Cela veut dire que votre repository doit définir une abstraction qui pourrait ressembler à ça :

 

 

Avec une implémentation pour Entity Framework :

 

 

Que l'on pourrait envisager de changer par une autre implémentation qui utilise des web services ... ou n'importe quoi d'autre :). C'est toute la beauté de ces abstractions, ainsi tout le code consommateur n'aura pas besoin de changer. Il faudra juste créer une nouvelle implémentation (et changer la construction).

Voici un exemple d'utilisation :

 

 

C'est tout ?

On pourrait être tenté de s'arrêter là, et la plupart des exemples que vous trouverez sur internet s'arrête là (voir même avant :( ...). Sauf que le repository va ENCORE plus loin. (et du coup, mon exemple du dessus est un peu ambigu).

En effet, ce qui est important également dans le repository c'est qu'il permet de manipuler les objets du domaine métier. Et non, les objets de la couche d'accès aux données. Et typiquement, on voit souvent des exemples de repository qui manipulent directement les POCO des ORMs comme Entity Framework ou (N)Hibernate. Et c'est une erreur car cela couple vos utilisateurs du repository à la couche d'accès aux données. Autrement dit, cela fait dépendre votre couche métier de la couche d'accès aux données.

La bonne pratique c'est l'inverse. Il faut que votre repository renvoie des objets du domaine métier, quitte à devoir créer ces objets à partir des POCO de votre ORM préféré. Et finalement, c'est ce que vous auriez fait si vous aviez utilisé une couche d'accès aux données plus rudimentaire, comme avec ADO.NET en mode roots ! Est-ce que votre auriez imaginé exposer un DataReader ou une DataTable directement dans votre repository ? J'espère bien que non !

Recomposez les objets métiers à partir de la couche d'accès aux données ! Un truc de ce genre :

 

 

la classe ProduitPOCO faisant partie de la couche d'accès aux données et la classe Produit faisant partie du domaine métier.

Certains me diront peut-être qu'ils n'ont pas accès à leur couche métier depuis leur couche d'accès aux données, car ils utilisent une architecture classique en couches :

 

 

Et je leur dirai : ERREUR.

Il s'agit d'un problème de gestion des dépendances, et nous en reparlerons dans un prochain billet lorsque nous aborderons l'inversion de dépendance.

Du coup, votre interface doit également faire partie du métier. Vous noterez que je ne l'ai pas appelée ICatalogueRepository ou IProduitRepository. C'est une interface du métier, elle doit avoir un nom du métier. Et comment s'appelle un dépôt de produit dans un site d'e-commerce ? Un catalogue, tout simplement :).

On peut aller encore plus loin, car vous avez dû constater que je n'ai pas décrit la notion de critère de recherche. Vous pouvez faire un peu ce que vous voulez dans ces critères de recherche, mais on rencontre parfois l'utilisation du pattern spécification. Il permet de séparer les responsabilités (on parle de ce principe dans le prochain billet, promis ), et incite à exprimer plus clairement les règles métiers dans le code.

 

Variante

Une variante du repository est le repository générique. Plutôt que d'avoir une interface comme ICatalogue, nous aurons une interface générique du genre :

 

 

L'intérêt est qu'on va pouvoir centraliser encore plus les accès aux données dans un unique repository. Par contre, si on veut pouvoir recomposer des objets métiers à partir des entités venant de la base de données, il va falloir créer une abstraction supplémentaire.

Du coup, le repository générique incite à utiliser directement les objets de la couche d'accès aux données dans le domaine métier, ce qui est mal. Il faut juste le savoir est créer ensuite des repositories dédiés qui peuvent réaliser l'assemblage en objets métiers.

A noter que dans le repository générique, l'implémentation du pattern spécification devient fortement recommandée.

 

Conclusion

Vous aurez compris que le repository vous permet de découpler votre domaine métier (et d'une manière générale votre application) de la couche d'accès aux données et donc de la persistance. Cela permet de changer de base de données ou de système de stockage avec le moins de douleur possible.

Côté consommateur, vous travaillez ainsi avec des collections d'objets métiers, comme si tout était en mémoire. Vous n'avez pas à vous préoccuper de la persistance.

Vous allez également réduire la redondance de code inhérente à la multiplication des accès en centralisant tout ça dans le repository.

 

Prochain billet(s) sur les principes SOLID.