Automatiser Ses Injections Avec Le DiscoveryModule De NestJs
6 min read / October 19, 2021
Dans l'article précédent, une architecture à été proposée pour faciliter le développement et le maintient du webhook de notre chatbot (cf Chatbot : Coder Proprement Un Webhook Avec NestJs).
En partant de cette base, on va voir comment faciliter l'ajout de nouveaux handlers en automatisant nos injections.
Pour le moment, pour charger les handlers dans le DispatchService
, ce que nous faisons est d'injecter les handlers un à un et de les stocker dans la liste de handlers :
Ça se fait, mais est-ce qu'il n'y aurait pas un moyen qui permettrait de le faire automatiquement pour ne pas se casser la nénette ? L'idée c'est de ne plus avoir à toucher la logique du DispatchService
seulement pour ajouter un handler dans la liste. Par exemple, est-ce qu'il y'a moyen de charger les handlers automatiquement une fois qu'ils ont été ajoutés dans le module ?
Aïe mais quel suspens, j'espère vraiment que l'auteur de l'article a une astuce pour ça sinon ça n'a aucun intérêt 😰
Tkt frelot(te) à la compote, j'ai tout prévu.
Commençons par
La découverte du DiscoveryModule
de NestJs 🛰🔍
Le DiscoveryModule
fait partie du core package de NestJs. C'est le module qui permet au framework de trouver les bons services à injecter en se basant sur leurs metadata.
Par exemple, NestJs sait qu'une classe est injectable grâce à l'annotation @Injectable()
. En allant voir dans le code source, on s'aperçoit que ce décorateur ne fait qu'ajouter des metadata à la classe annotée :
Je rentre pas dans les détails de la Metadata Reflection API parce que ça ferait un article bien trop gros.
Partant de là, on peut utiliser la même mécanique pour marquer les classes qui sont des handlers. Il suffit de leurs ajouter une metadata maison puis de les récupérer grâce au DiscoveryService
et sa méthode getProviders()
.
Aparté sur la méthode getProviders()
Comme son nom l'indique, la méthode récupère l'ensemble des providers injectés dans l'application. Et elle fait ça sans distinction de module. ✊🏻✊🏼✊🏽✊🏾✊🏿
Par exemple, si on considère les modules suivants :
Et qu'on les injecte dans notre AppModule
:
Même si ProviderA
et ProviderB
n'ont pas été exportés depuis leur module respectif, on les retrouvera quand même dans la liste de handlers retournée par la méthode getProviders()
.
Pour être précis, la méthode retourne une liste de InstanceWrapper
qui possèdent 2 propriétés intéressantes :
metatype
: contient les métadonnées de la classe providée. On va utiliser la Metadata Reflection API pour aller chercher la métadonnée qui nous intéresse.instance
: contient une instance de la classe providée. Dans notre cas, ça sera une instance de handler si le provider possède la bonne métadonnée.
Ceci étant dit, on peut faire une première ébauche pour trouver les providers qui sont des handlers en utilisant le DiscoveryService
:
L'injection sans pression 💉🍻
Maintenant qu'on a notre stratégie, on peut passer à l'implémentation.
En reprenant la logique plus haut, on va commencer par créer notre propre décorateur pour injecter notre metadata maison :
On peut ensuite l'appliquer à chacun de nos handlers.
En reprenant ceux de l'article précédent, ça nous donne :
Il ne nous reste plus qu'à adapter le DispatchService
pour filtrer les providers selon la bonne metadata :
Épilogue
Pour résumer, au lancement du programme, le DispatchService
va parcourir l'ensemble des providers répertoriés dans l'application pour ne garder que ceux qui ont notre metadata maison ACTION_HANDLER_METADATA_KEY
et les stocker dans sa liste de handlers.
Créer un nouveau handler est maintenant plus simple que jamais : il suffit de
- Créer une nouvelle classe injectable.
- Annoter la classe avec notre décorateur
ActionHandlerDecorator
. - Ajouter la classe dans les providers de notre application.
Le DispatchService
se charge du reste, sans qu'on ait à le modifier.