Créer un simple moteur de workflow dynamique en Groovy
Inspiré en partie par l'article Design Pattern: Design a Simple Workflow using Chain of Responsibility Pattern, cet article décrit la conception d'un simple moteur de workflow en langage Groovy.
Groovy est un langage appréciable car, du fait de sa nature dynamique et de son expressivité, il permet de mettre en oeuvre rapidement des concepts et des idées ! En l'occurrence, nous voulons simplement pouvoir enchaîner l'exécution de code, au travers d'un workflow qui peut être décrit de manière externe grâce à un DSL (Domain-Specific Language) Groovy.
Notre moteur de workflow, dont le code Groovy est proposé en fichier attaché, possède les caractéristiques suivantes :
- un workflow comprend un certain nombre d'activités écrites en Groovy ou en Java ; identifiée par un nom, une activité indique quelle est l'activité suivante à exécuter si besoin, en renvoyant une chaîne de caractères devant être un nom d'activité ;
- un workflow peut être défini dans un fichier externe, sous la forme d'un DSL, ou bien par une Closure Groovy ;
- afin de partager des informations entre les différentes activités, chaque activité peut accéder à un contexte prenant la forme d'une Map Groovy.
S'intégrant parfaitement avec Java, rien n'empêche d'exécuter du code Java à partir d'une activité de workflow, ou même d'invoquer une activité développée en Groovy ou en Java, et présente dans le classpath.
Un premier exemple de définion de workflow
Voici un premier exemple de workflow :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Ce workflow comporte trois activités nommées init
, next
et end
; chacune d'elles est implémentée par une Closure Groovy (à la place d'une Closure, on pourrait également trouver une classe, ou une chaîne de caractères). Les deux premières Closures renvoient une chaîne de caractères pour indiquer quelle est l'activité suivante à exécuter, tandis que la troisième retourne null
, ce qui mettra fin à l'exécution du workflow.
Exécuter un workflow
Vous pouvez exécuter un workflow, décrit au travers du DSL dans un fichier séparé (j'utiliserai l'extension .swf.groovy
pour de tels fichiers), grâce au fichier source SimpleWorkflow.groovy
associé à ce billet.
Pour cela, exécutez la commande groovy
(venant avec l'installation de Groovy), en passant comme arguments, le fichier Groovy SimpleWorkflow.groovy
, le nom du fichier DSL .swf.groovy
, ainsi que le nom de l'activité de départ :
1
|
|
L'exécution du workflow donné plus haut, avec comme activité de départ init
, affiche ceci :
1 2 |
|
En Groovy, l'exécution du workflow défini dans le fichier Exemple1.swf.groovy
donnerait ceci :
1 2 |
|
Second exemple : trouver un nombre mystérieux
Donnons un nouvel exemple de workflow défini par le fichier NombreMysterieux.swf.groovy
(disponible en fin de billet) ; très connu, il s'agit d'un jeu dont but est de vous faire deviner un nombre mystérieux, en vous donnant des indications par rapport à des nombres que vous saisissez dans l'invite de commandes.
Le workflow comporte trois activités : init
, qui détermine un nombre aléatoire et le place dans le contexte, puis initialise un compteur de propositions ; input
, qui se charge de la saisie utilisateur et teste la valeur soumise afin d'afficher un message d'aide, et d'orienter l'exécution du workflow ; et enfin success
, qui correspond à l'activité signalant la réussite du jeu.
Notez que le découpage en activités de ce workflow, ainsi que les activités elles mêmes sont tout à fait arbitraires ; chaque activité devrait avoir un rôle précis et bien identifié, et même pourvoir être réutilisée dans d'autres workflows.
Utiliser des classes comme activités
En plus de définir le code d'une activité sous la forme d'une Closure Groovy, il est aussi possible de fournir des classes, dans le DSL qui décrit un workflow, en tant qu'implémentations d'activités ; de cette manière, des activités prédéfinies pourront être facilement utilisées et réutilisées dans un ou plusieurs workflow(s).
Si dans le DSL, un nom d'activité est suivi d'une classe, SimpleWorkflow tentera d'instancier un objet de cette classe et d'appeler une méthode nommée call
sur cet objet, en lui transmettant le contexte du workflow en paramètres. En Groovy, c'est la notion de classe callable.
Examinons un nouvel exemple de workflow qui utilise les classes GroovyActivityTest et JavaActivityTest comme implémentations d'activités :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Ce workflow liste également toutes les manière d'implémenter une activité : par une simple chaîne de caractères, une Closure Groovy, ou bien une classe (Java ou Groovy).
La classe GroovyActivityTest
est écrite en Groovy, et possède une méthode call
:
1 2 3 4 5 6 7 8 9 10 |
|
Si obj
représente un objet d'une telle classe, l'instruction obj(args)
en langage Groovy, appellera automatiquement la méthode call
de l'objet en transmettant l'argument args
.
Cela fonctionne également avec une classe Java, comme ici, avec la classe JavaActivityTest
: au moment de l'exécution de l'activité "3 - Classe Java", le moteur de workflow appellera la méthode call
de cette classe sur un objet de ce type. Le code de la classe JavaActivityTest
est repris ci-dessous :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Notez que, comme c'est le cas pour l'activité "4 – Closure" qui utilise une Closure comme implémentation, il reste possible de faire appel à des classes issues de librairies Java.
Tracer l'exécution des activités
SimpleWorkflow propose également un moyen simple de tracer l'exécution des activités : plus précisément, il s'agit de pouvoir exécuter automatiquement une Closure associée au nom beforeActivity
avant l'exécution d'une activité, et une Closure associée au nom d'activité afterActivity
juste après l'exécution d'une activité.
Pour mettre en œuvre ce comportement, il suffit d'ajouter les activités beforeActivity
et afterActivity
comme dans l'exemple ci-dessous :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
L'exécution de ce workfow produira ceci en sortie :
1 2 3 4 5 6 7 8 9 |
|
Les noms beforeActivity
et afterActivity
ne sont pas figés : ceux-ci sont utilisés par défaut comme valeurs des propriétés beforeActivityName
et afterActivityName
d'un objet SimpleWorkflow
; il est bien entendu possible de changer les valeurs de ces propriétés, juste avant l'exécution du workflow, à condition de prévoir des activités portant des noms correspondant dans le workflow.