Dans cet article, nous allons explorer les différentes façons de mettre en œuvre le motif de conception pipeline en partant des bases jusqu’à des solutions plus complexes.
Le motif pipeline
Le motif pipeline est un motif de conception logicielle qui offre la possibilité de construire et d’exécuter une séquence d’opérations.
Ce pattern est mieux utilisé en conjonction avec le pattern plugin, pour construire dynamiquement le pipeline au démarrage de l’application.
Séquence
L’implémentation la plus basique d’un pipeline serait une simple séquence d’opérations.
L’interface d’une opération peut être invoquée pour traiter des données.
Le pipeline traite chaque opération une par une. La classe du pipeline implémente également l’interface IOperation afin qu’elles puissent être combinées.
L’opération peut être écrite dans une classe dédiée.
Ou utiliser un wrapper pour créer automatiquement une opération à partir d’un lambda.
Les opérations du pipeline doivent être enregistrées avant que le pipeline soit invoqué.
Circuit Breaker
La première fonctionnalité que vous voulez ajouter à votre pipeline est d’ajouter un disjoncteur.
Chaque opération retournera le résultat : échec ou succès.
Si une opération échoue, l’exécution du pipeline doit s’arrêter.
Asynchrone
Une autre exigence pourrait être d’avoir un pipeline qui peut traiter des opérations asynchrones.
Chaque opération devra maintenant appeler l’opération suivante dans le pipeline, après avoir fini de traiter les données.
Le pipeline est un peu plus complexe, car il doit définir l’opération suivante lorsqu’une nouvelle opération est enregistrée. Une autre solution est d’utiliser un constructeur.
Cette opération est asynchrone et s’exécutera dans un thread dédié, lorsque le temps est écoulé, elle invoquera l’opération suivante pour continuer le pipeline.
L’opération générique peut être utilisée à la fois avec une action simple, ou utiliser un coupe-circuit intégré si vous utilisez une fonction.
Cet exemple simple utilise plusieurs des fonctionnalités que nous avons implémentées.
Maintenant vous savez comment rendre votre pipeline asynchrone !
Ce serait encore mieux s’il y avait un autre callback à l’opération précédente pour que vous puissiez avoir le résultat allant à contre-courant dans le pipeline.
Plugin
La principale raison d’utiliser le patteur de conception de pipeline est souvent l’exigence de pouvoir ajouter des plugins qui vont soit ajouter des opérations à un pipeline existant ou accrocher une opération au milieu de celui-ci.
Le pipeline est vraiment basique, mais cette fois, les opérations sont exposées.
Prenons une application simple avec un pipeline qui n’affichera que les 3 étapes dans la console. Cette application supporte également des plugins pour modifier le pipeline.
Le premier plugin va accrocher une autre opération dans le deuxième slot du pipeline.
Le deuxième plugin va ajouter une nouvelle opération à la fin du pipeline.
L’application et les plugins sont mis ensemble, nous pouvons invoquer le pipeline.
Batch
Une autre fonctionnalité utile est de pouvoir traiter des données par lot dans le même pipeline que des éléments individuels.
Le pipeline batch enveloppe un pipeline et appelle chaque opération sur chaque item.
Chaque item est enveloppé, ainsi nous pouvons nous souvenir du résultat du disjoncteur.
Cette opération vérifie si l’entier a l’ordre de grandeur requis.
Le pipeline va vérifier l’ordre de grandeur d’un lot d’entiers.
Le pipeline n’invoque l’opération suivante que pour les éléments qui n’ont pas échoué.
High Performance Pipeline
Le Pipeline Design Pattern peut aussi faire référence à une architecture logicielle beaucoup plus spécifique et orientée vers la performance.
Certains projets utilisent un pipeline pour optimiser le traitement d’une énorme quantité de données en exécutant chaque opération du pipeline dans un thread dédié.
Chaque thread consommera et produira des données à partir d’une file d’attente concurrente qui fera office de tampon.
Cette fois, nous utiliserons des opérations asynchrones avec un disjoncteur.
Si l’opération est réussie, elle devrait invoquer next, ou se terminer en cas d’échec.
Le pipeline est conçu pour contourner le coupe-circuit. Succès ou échec, il continuera toujours la séquence du pipeline parent et appellera l’opération suivante.
Le scénario est un peu complexe, donc je ne vais pas tout expliquer. L’idée est d’avoir différents threads pour traiter les commandes entrantes. Quand le traitement d’une commande est terminé, nous vérifions le statut de la commande.
Chaque processeur de commande est isolé dans un thread dédié, donc vous pouvez optimiser la façon dont vous stockez les données et avoir un accès direct à la mémoire sans utiliser de verrou.
Le processeur de commande de paiement est le seul thread qui peut accéder aux soldes des utilisateurs. Il peut obtenir ou mettre à jour n’importe quel solde sans se soucier des problèmes de concurrence.
Le pipeline traite la séquence d’opérations aussi rapidement que possible.
Conclusion
Le motif de conception pipeline a beaucoup de façons très différentes d’être mis en œuvre, d’une simple chaîne de commande à un flux de travail plus complexe.
Plusieurs fonctionnalités peuvent être requises telles que le disjoncteur, l’asynchrone ou le tampon, mais la plupart du temps, vous voulez utiliser des pipelines lorsque vous voulez avoir des plugins qui peuvent s’accrocher dans le flux d’exécution du pipeline.