Création de graphes Neo4j dans un script Groovy, avec sucres syntaxiques
Découvrant les bases de données de type graphe avec la base de données Neo4j, il était naturel pour moi de chercher à en découvrir l'API au moyen de scripts Groovy. Les scripts présentés dans cet article utilisent le système GRAPE qui permet de ramener toutes les dépendances dont on a besoin, et ainsi d'être assez vite au coeur de l'action.
Je vous présenterai également quelques sucres syntaxiques (en Groovy) pour augmenter la clarté du code pour la création de graphes Neo4j.
Pour commencer, voici un script Groovy s'inspirant de l'exemple Hello World du manuel de Neo4j :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
Ce script crée une base de données Neo4j de type embedded si elle n'existait pas auparavant dans le répertoire indiqué, ajoute deux noeuds liés par une relation au sein d'une transaction, puis enfin affiche le résultat de l'exécution d'une requête Cypher sur la base de données.
Notez que dans ce script, on ajoute une propriété message sur chacun des noeuds, ainsi que sur la relation qui les lie, avec un appel de méthode setProperty
. La relation est identifiée par la valeur d'énumération RelTypes.KNOWS
.
Pour la requête Cypher, qui est plutôt simple, on recherche les noeuds liés entre eux par une relation dirigée libellée "KNOWS" ; à la suite de l'exécution du script, vous devriez voir s'afficher ceci (pour la toute première exécution) :
+-----------------------------------------------------------------------------------------+ | n | r | m | +-----------------------------------------------------------------------------------------+ | Node[1]{message:"Hello"} | :KNOWS[0]{message:"brave Neo4j"} | Node[2]{message:"World!"} | +-----------------------------------------------------------------------------------------+ 1 row
Le premier sucre syntaxique que nous allons appliquer à notre script est celui décrit par le billet intitulé Basic Neo4j through Groovy. L'idée est de faciliter la définition et la lecture des propriétés que l'on peut avoir aussi bien sur les noeuds que sur les relations, grâce à un zeste de méta programmation Groovy :
1 2 3 |
|
Sans entrer dans le détail, on indique à l'interface PropertyContainer
(qui est l'interface parent des interfaces Node
et de Relationship
) comment se définit et se lit une propriété que l'on utiliserait avec l'opérateur dot sur un objet de ce type.
Ainsi, plutôt que d'écrire node.setProperty('message', 'Hello')
, on est en droit d'écrire :
1
|
|
C'est comme si message
était une propriété à part entière de l'objet Node
.
De même, pour la lecture, au lieu d'écrire node.getProperty('message')
, on peut simplement utiliser node.message
.
On peut ainsi rendre le code plus lisible !
Voici à nouveau notre script, avec l'application de ce que nous venons juste de voir :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
Dans notre script, la création d'un noeud s'effectue en deux étapes : une instance de Node
est créée à partir de l'objet db
, qui est de type GraphDatabaseService
, puis ensuite on positionne une ou plusieurs propriétés, à raison d'une écriture par propriété. Ne serait-il pas intéressant de pouvoir le faire en une seule fois, sachant que l'interface GraphDatabaseService ne dispose pas d'une telle méthode ?
C'est là qu'intervient à nouveau la magie de la méta programmation ; ajoutons ce comportement, de cette manière :
1 2 3 4 5 6 7 |
|
Ici, nous ajoutons une nouvelle méthode, createNode
, prenant une map comme paramètre afin de passer les propriétés à ajouter à un noeud, créé en interne par la méthode createNode()
sans argument. Dans le code de la nouvelle méthode, notez aussi de quelle manière nous ajoutons les propriétés en utilisant la forme node."$k" = v
, où k
est le nom de la propriété et v
sa valeur.
Du coup, nous pouvons créer un noeud, en transmettant les différentes propriétés à lui ajouter, comme ceci :
1
|
|
Avec ce second sucre syntaxique en action, voici la nouvelle version du script :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
Portons maintenant notre attention sur la manière de créer une relation entre deux noeuds, pour la rendre plus expressive.
Pour cela, je vais utiliser une redéfinition de l'opérateur rightShift (>>
) pour que :
1
|
|
puisse aussi s'écrire :
1
|
|
Je donne ci-après le code qui permet de réaliser cela :
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Pour comprendre ce que fait ce code, réécrivons l'affectation de la variable relationship
en considérant que l'utilisation de l'opérateur >>
correspond à l'appel de méthode rightShift
, et en tenant compte de l'ordre d'évaluation :
1
|
|
Le premier appel à rightShift
fait intervenir le redéfinition de cet opérateur sur un type Node
, et renvoie une instance de la classe uilitaire RelationshipHelper
; cette classe sert à contenir les références intermédiaires que sont le noeud de départ et la relation. Et le second appel à rightShift
, qui est la redéfinition de >>
mais cette fois dans la classe RelationshipHelper
, finalise la création de la relation : en effet, tous les éléments sont réunis pour créer la relation à partir du noeud de départ vers le noeud cible transmis en paramètre à rightShift
.
Notre script, avec l'utilisation de l'opérateur >>
, s'écrit maintenant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
|
Ce script crée une relation entre les noeuds firstNode
et thirdNode
en utilisant la chaîne de caractères 'KNOWS'
; cela est rendu possible du fait de la redéfinition de l'opérateur rightShift
avec un argument de Closure de type String
:
1 2 3 |
|
En utilisant deux redéfinitions, l'une prenant un argument de Closure de type RelationshipType
et l'autre un argument de type String
, on tire partie du support multi méthodes de Groovy (voir Groovy Goodness: Multiple Overloaded Operator Methods for Nice API).
Dans le cas de l'utilisation d'une chaîne de caractères pour établir une relation, l'objet relation créé est une relation dynamique nommée de type DynamicRelationshipType
.
Les sucres syntaxiques que je vous ai présentés dans cet article sont juste quelques possibilités d'utilisation du langage Groovy pour augmenter la lisibilité du code dans le cas de la création de graphes Neo4j, et il est certain que l'on peut explorer d'autres pistes !