Faites de l’injection de dépendances entre les packages une tâche triviale avec cette configuration
TL ; DR : En utilisant
injectable
,injectable_generator
etget_it
nous créons unget_it
instance et passez-la à travers différents packages et demandez à chaque package d’appliquer sa configuration à cetteget_it
exemple.
Généralement, la configuration de l’injection de dépendances implique une configuration qui ressemble à ce qui suit :
Nous récupérons le get_it
instance et utiliser une magie de génération de code sophistiquée pour faire bouger les choses en arrière-plan, et nous annotons nos classes avec des choses comme @Injectable
ou alors @LazySingleton
pour les rendre disponibles via di.
Tout cela est un moyen pratique et facile de faire notre di. C’est bien mieux que d’avoir à tout enregistrer manuellement tout le temps.
Mais nous ne parlons que d’un seul paquet. Habituellement, c’est votre application Flutter, par exemple.
Que se passe-t-il si vous avez un projet dans lequel vous contrôlez plusieurs packages et que vous souhaitez toujours utiliser cette solution di facile ?
Vous souhaiterez peut-être qu’une telle structure de packages assouplisse les dépendances de votre projet ou applique une certaine architecture (préfiguration ;)).
Eh bien, j’ai une solution pour vous!
Avant de commencer, je souhaite clarifier à quoi ressemblera le produit final avec l’organigramme suivant :
Si vous y réfléchissez, cela prend tout son sens car App
dépend de tout le reste des packages.
C’est aussi simple que ça.
Noter: Pour l’exemple donné, toute cette configuration est extrêmement exagérée, mais c’est pour aider l’idée à passer.
Commençons par définir une application simple. Mon exemple de prédilection est généralement un compteur, alors allons-y.
Notre application sera un compteur de base incrémentable dont la valeur est stockée en mémoire. Heureusement, Flutter en fournit déjà une partie importante lorsque nous créons un nouveau projet.
L’application sera divisée en quatre packages:
- une
counter
paquet qui exporte unCounter
interface et uneCounterStorage
interface, et une implémentation pourCounter
interface nomméeCounterImpl
- une
counter_storage
package qui implémente l’interface de stockage à partir ducounter
forfait. - une
logger
paquet qui exporte unLogger
interface et définit uneLoggerImpl
la mise en oeuvre. - un
app
paquet généré par Flutter.
Voici un graphique montrant comment ces packages dépendent les uns des autres :
Le app
couche saisit le compteur et sa mise en œuvre à partir de counter
et utilise également le logger
paquets Logger
.
La raison counter_storage
dépend de counter
c’est parce qu’il a besoin de CounterStorage
l’interface de sorte que counter_storage
peut faire le CounterStorageImpl
la mise en oeuvre.
En pratique, voici comment l’exécution du code se déroulera :
L’injection de dépendance est la façon dont nous procédons. Alors, voici étape par étape:
Étape 1 : Configurer les packages
Par souci de cohérence, nous ferons l’implémentation DI exactement comme dans les diagrammes ci-dessus. Vous pouvez trouver une implémentation finie de cette étape ici.
Pour créer les packages, vous pouvez utiliser les commandes suivantes (dans l’ordre) :
mkdir dart-easy-dicd dart-easy-di
# 1st package
dart create counter
# 2nd package
dart create counter_storage
# 3rd package
dart create logger
# Finally, the app package
flutter create app
(Noter: vous pouvez copier-coller tout ce bloc de commandes et le coller dans votre terminal et l’exécuter)
Nous ajouterons les classes nécessaires à nos packages pour terminer cette étape.
Au counter
package, ajoutez les classes suivantes :
Le CounterImpl
est une implémentation de base de Counter
qui utilise CounterStorage
comme dépendance.
Nous devons exporter le Counter
et CounterStorage
interfaces, nous le faisons donc ici :
La première configuration du package est terminée ! Au suivant. Configurons le CounterStorage
forfait.
Ajoutez les fichiers suivants :
Nous devons ajouter la dépendance sur counter
afin que nous puissions l’importer.
Cela rend notre counter_storage
paquet fait pour l’instant. Vous remarquerez que nous n’exportons rien, alors comment y accéder finalement ? Avec la magie DI, bien sûr !
Nous y reviendrons dans les étapes suivantes. Prenons soin de logger
suivant. Ajoutez les fichiers suivants :
Enfin, nous exportons le Logger
interface:
Très bien! Maintenant, tout ce qui reste est le app
forfait. Pour cette étape, nous allons ajouter les dépendances dans pubspec
:
Étape 2 : Configurer DI avec GetIt et Injectable
Vous pouvez trouver une implémentation finie de cette étape ici.
L’idée ici est d’exporter une méthode qui prend un get_it
instance et configure tout ce dont il a besoin.
Tout d’abord, tous nos packages ont besoin de quelques dépendances. Ajoutons-les avec le script fantaisiste ci-dessous :
# foreach dir
for d in */; do
cd "$d"
echo "$d"
dart pub add get_it injectable
dart pub add --dev injectable_generator build_runner
echo "\n"
cd ..
done
Ne vous inquiétez pas, cela ne piratera pas votre ordinateur (probablement). Il installera certaines dépendances requises pour notre solution DI sur chacun de nos packages, à savoir get_it
, injectable
, injectable_generator
et build_runner
.
Ensuite, pour chacun des counter
, counter_storage
et logger
packages, nous allons ajouter ce qui suit (ignorez les erreurs de votre éditeur pour l’instant) :
Ensuite, nous exportons ceci configureDependencies
fonction dans chacun de counter/lib/counter.dart
, counter_storage/lib/counter_storage.dart
et logger/lib/logger.dart
:
Enfin, nous mettons tout en place dans app
:
Aux lignes 21 à 23, vous pouvez voir que nous appelons chacun de nos colis configureDependencies
fonctions que nous venons d’exporter. Nous aliasons les importations avec le as
mot-clé pour éviter les conflits de nommage.
Une dernière étape consiste à appeler notre app
c’est configureDependencies
fonction dans app/lib/main.dart
:
Nous avons fait 95 % de la configuration dont nous avons besoin, mais nous avons encore des erreurs gênantes dans le di.dart
des dossiers. Pour vous en débarrasser, exécutez le script suivant :
# foreach dir
for d in */; do
cd "$d"
echo "$d"
dart run build_runner build
echo "\n"
cd ..
done
Vous devrez exécuter ce script chaque fois que vous mettez à jour vos dépendances de quelque manière que ce soit. Des exemples de cela sont l’ajout d’un @Injectable
tag, ajouter une nouvelle dépendance à une classe, etc.
Je vais vous montrer comment vous pouvez ajouter ce script en tant que fonction facile à exécuter dans votre configuration bash ou zsh ici.
L’exécution de ce script entraînera l’apparition d’un nouveau fichier dans le dossier de chaque package. di/
répertoire appelé di.config.dart
. Ce fichier contient les associations réelles entre les classes qui autorisent DI. Les erreurs devraient avoir disparu maintenant, mais nous n’avons pas encore fini.
Vous devez ignorer ce fichier dans votre .gitignore
alors assurez-vous d’ajouter **/di/*.config.dart
Ou quelque chose du genre. perso j’en fais un .gitignore
déposer sous dart-easy-di/
(qui contient tous nos packages) et ajoutez l’instruction ignore uniquement ici.
Donc, la seule chose qui reste à ce stade est d’annoter nos fichiers.
Étape 3 : Annoter avec Injectable
Vous pouvez trouver une implémentation finie de ceci ici.
Nous annotons nos implémentations avec les balises pertinentes. Ceci comprend CounterImpl
, CounterStorageImpl
et LoggerImpl
:
Avec cela, nous avons associé nos implémentations à leurs interfaces. Nous n’avons même pas eu besoin d’exporter les implémentations !
Nous devons exécuter le build_runner
script à nouveau depuis que nous avons changé les dépendances :
# foreach dir
for d in */; do
cd "$d"
echo "$d"
dart run build_runner build
echo "\n"
cd ..
done
Noter: vous pouvez recevoir des avertissements lors de l’exécution de ce script concernant certaines dépendances non enregistrées. Vous pouvez les ignorer en toute sécurité.
Nous avons maintenant officiellement terminé la préparation. Nous pouvons commencer à utiliser tout cela dans notre app
.
Utiliser nos forfaits dans l’application Flutter
Vous pouvez trouver une implémentation finie ici.
Dans app/lib/main.dart
nous procédons comme suit :
Les lignes 3 à 11 sont celles où nous ajoutons les importations pour nos packages. Vous remarquerez également le di.dart
l’importation de fichier est aliasée comme app
. Cela permet d’éviter les conflits d’espace de noms.
Sur les lignes 16 -> 18, nous utilisons notre instance DI pour obtenir un compteur et un enregistreur. Ensuite, nous utilisons ceux des lignes 22, 23 et 41. Essayez-le et voyez si l’enregistreur fonctionne réellement !
Si vous rencontrez des erreurs avec le get_it
exemple, en exécutant le build_runner
script à nouveau pourrait les résoudre.
Un scénario plus réaliste
Vous pouvez trouver la mise en œuvre finale de cette étape ici.
Bien que cela fonctionne, ce n’est pas tout à fait correct. Nous avons provoqué la app
package dépend directement de counter_storage
Bien que app
n’interagit pas du tout avec lui (autre que d’appeler son configureDependencies
.
Comment pouvons-nous faire en sorte de ne pas casser notre graphique de dépendance tout en gardant DI tel qu’il est ?
La réponse est avec le di
couche. Il s’agit d’un forfait dont le seul but est de recevoir un get_it
instance et de le transmettre à tous les packages qui en ont besoin.
De cette façon, votre app
couche ne dépend que de di
et di
dépend des autres et distribue votre solution DI.
app
dépendra bien sûr toujours de counter
et logger
car il a besoin de leurs interfaces exportées.
Pour implémenter notre di comme dans le diagramme ci-dessus, procédez comme suit (ouvrez un terminal dans dart-easy-di
:
# Create the package
dart create di# Install its dependencies
cd di
dart pub add get_it injectable
dart pub add --dev injectable_generator build_runner
Ajoutez les dépendances suivantes à son pubspec.yaml
et maintenant nous pouvons supprimer le counter_storage
dépendance de app/pubspec.yaml
.
Ensuite, nous avons mis en place le di
paquet avec son propre di.dart
fichier, qui ressemble à ceci :
Cela ressemble à ce que nous avons fait dans app/lib/di/di.dart
, n’est-ce pas ? La prochaine étape consiste à exporter ce configureDependencies
fonction de di
importez-le dans app
puis appelez-le. Voici les étapes à suivre :
Exporter depuis di
forfait:
Importer dans app
(vous devrez peut-être exécuter dart pub get
après)
Mise à jour app/lib/di/di.dart
Et enfin, lancez le build_runner
scénario à nouveau.
# foreach dir
for d in */; do
cd "$d"
echo "$d"
dart run build_runner build
echo "\n"
cd ..
done
Si vous avez tout fait correctement (et bien sûr vous l’avez fait, vous êtes un individu incroyablement intelligent), alors vous avez implémenté l’injection de dépendance interpackage.
Voici la commande pour configurer le build_runner
fonction dans votre configuration shell. Cela fera courir le build_runner
scénario plus facile.
echo "
function build_all {
for d in */; do
cd "$d"
echo "$d"
dart run build_runner build
echo "\n"
cd ..
done
}
" >> ~/.zshrc
Remplacer .zshrc
avec votre propre fichier de configuration shell. Après avoir exécuté ceci dans votre terminal, redémarrez votre terminal ou exécutez source ~/.zshrc
.
Maintenant, vous pouvez taper build_all
dans votre terminal lorsque vous êtes dans le dart-easy-di
répertoire, et il exécutera l’étape de construction pour tous les packages pour vous.
Que pensez-vous de cette solution ?
Vérifier mon dépôt pour la réalisation complète.