Neo4j la base de données graphe


Les bases de données relationnelles ont pu résoudre la plupart des problématiques de stockage pour les données de nos applications. Mais de temps en temps Il se peut que cela devienne très compliqué de représenter de façon relationnelle certains types de données. Par exemple un graphe dynamique d’objet du même type. Je vous invite donc, à découvrir une autre face du monde des bases de données.

Présentation de Neo4j

Imaginez une application de réseau social comme Facebook, Viadeo ou Linkedin dans laquelle l’utilisateur peut se lier avec des amis. Cet utilisateur veut savoir quels amis il a en commun avec d’autres amis. Grâce à un graphe, il pourrait facilement voir ces relations. En voici un exemple basique :

Graph example 1

L’implémentation du schéma ci-dessus dans une base de données relationnelle n’est pas facile. Il existe plusieurs façons de le faire, comme le Pattern Querie pour faire des résolutions, mais cela reste compliqué à utiliser. Pour résoudre ces problèmes plusieurs bases de données de type graphe existent, dont la célèbre base Neo4j ainsi que HyperGraphDB et InfoGrid.

Dans un graphe nous allons pouvoir stocker deux types d’informations : des nœuds et des liens. En anglais des nodes et des edges. Chaque nœud peut posséder plusieurs liens qui pointent sur d’autres nœuds. C’est grâce à cela que les relations peuvent se faire entre les nœuds. Elles vont ainsi nous permettent de les organiser. De plus chaque nœud peut avoir plusieurs propriétés ou attributs pour stoker sous forme de clé/valeur nos données.

En bref : Un graph stocke des données dans des nœuds qui ont des propriétés.

Les relations organisent les nœuds entre eux. Nous allons pouvoir trier nos données sous forme de liste, d’arbre ou d’une façon plus libre en fonction de nos besoins. Elles peuvent avoir plusieurs directions, soit sortante, soit entrante ou les deux à la fois. De même que les nœuds, elle peuvent elle aussi avoir des propriétés pour nous faciliter l’organisation.

En bref : Les nœuds sont organisés par des relations qui ont-elles même des propriétés.

Il existe deux façons de récupérer des données dans un graphe. Soit par une traversée, soit avec des index. Pour la première méthode il faut « traverser » graphe de nœuds en nœuds en fonction d’un algorithme. On peut définir plusieurs options lors de la traversée : récupérer le nœud en fonction d’une valeur de ses propriétés, si le nœud possède au moins une relation sortante, etc. Si on prend l’exemple ci-dessus, on pourrait créer une traversée pour répondre à cette question : Quels amis de Martin ont au moins deux amis en commun avec lui. Voici le sous graph qui nous serait retourné :

Graph exemple 2

Pour résoudre cette problématique il suffit de tester s’il y a au moins deux relations sortantes pour chaque nœud.

En bref : Une traversée navigue dans le graphe à partir d’un nœud et identifie les chemins ou sous chemins avec les nœuds ordonnés en fonction d’options.

La deuxième manière de récupérer un nœud ou une relation de façon plus spécifique est d’utiliser les index. Grâce à cela nous allons pouvoir récupérer un nœud directement en fonction d’une valeur de ses propriétés. Si on reprend l’exemple plus haut, la base de données pourra nous retourner un nœud en fonction son attribut « nom ».

En bref : Un index est mappé par les propriétés des nœuds et des relations.

Et Neo4j dans tout ça ?

La base de données permet de gérer tous ces types d’objet, nœuds, relations et index. Et grâce à des algorithmes, des outils internes et des modules externes comme Apache Lucuene, Cypher, ou Gremlin la récupération de nos données est plus facile.

Mise en pratique

Nous allons mettre en pratique l’exemple de la présentation en utilisant la version embarquée de Neo4j. Créez un projet Maven avec votre IDE préféré et rajoutez cette dépendance dans le pom :

<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j</artifactId>
    <version>1.6</version>
</dependency>

Dans Neo4j chaque relation doit être typée. Nous allons donc créer une énumération qui représentera tous les types de notre graphe :

public enum RelTypes implements RelationshipType {
    FRIEND
}

Ensuite créez une classe avec la classique méthode main. Pour créer et démarrer la base de données Neo4j en mode embarqué, il suffit de déclarer un objet de type GraphDatabaseService et de l’instancier avec la classe EmbeddedGraphDatabase en passant comme paramètre au constructeur le chemin de la base :

public class Main {
private GraphDatabaseService graphDB;
    public Main() {
        graphDB = new EmbeddedGraphDatabase(DB_PATH);
    }
    public void shutDownDB() {
        graphDB.shutDown();
    }
    public static void main(String[] args) {
        Main main = new Main();
        main.shutDown();
    }
}

La méthode shutdown de notre base de données permet de l’éteindre correctement. La création des nœuds est très facile. Il suffit de déclarer pour chaque nœud un nouvel objet de type Node créé à partir de notre objet graphe :

Node martin = graphDB.createNode();

Pour lui affecter une propriété, comme le nom par exemple, il faut appeler la méthode setProperty qui prend en paramètres une clé et une valeur.

martin.setProperty("name", "Martin");

Pour indexer le nœud « Martin » afin de le récupérer facilement, voici la démarche à faire :

IndexManager indexManager = graphDB.index();
Index<Node> users = indexManager.forNodes("users");
users.add(martin, "name", martin.getProperty("name"));

Pour créer une relation entre deux nœud une méthode createRelationship est disponible dans chaque objet de type Node. Elle prend en paramètre le nœud à lier et un type. Voici un exemple qui lie comme ami Martin avec Romain :

Node romain = graphDB.createNode() ;
martin.createRelationshipTo(romain, RelTypes.FRIEND);

Puisque nous allons créer plusieurs nœuds et relations, nous devons les encapsuler dans une transaction. Car si une erreur se produit pendant une insertion il faut invalider les présentes. Voici toutes les insertions de l’exemple :

Transaction tx = graphDB.beginTx();
try {
    // Creation des Noeuds
    Node martin = graphDB.createNode();
    martin.setProperty("name", "Martin");
    // Indexation de martin
    users.add(martin, "name", martin.getProperty("name"));
    Node romain = graphDB.createNode();
    romain.setProperty("name", "Romain");
    Node matthieu = graphDB.createNode();
    matthieu.setProperty("name", "Matthieu");
    Node lois = graphDB.createNode();
    lois.setProperty("name", "Lois");
    Node sebastien = graphDB.createNode();
    sebastien.setProperty("name", "Sebastien");
    Node brice = graphDB.createNode();
    brice.setProperty("name", "Brice");
    // Creation des Relations
    martin.createRelationshipTo(romain, RelTypes.FRIEND);
    martin.createRelationshipTo(matthieu, RelTypes.FRIEND);
    martin.createRelationshipTo(lois, RelTypes.FRIEND);
    martin.createRelationshipTo(sebastien, RelTypes.FRIEND);
    martin.createRelationshipTo(lois, RelTypes.FRIEND);
    romain.createRelationshipTo(lois, RelTypes.FRIEND);
    romain.createRelationshipTo(sebastien, RelTypes.FRIEND);
    matthieu.createRelationshipTo(romain, RelTypes.FRIEND);
    matthieu.createRelationshipTo(sebastien, RelTypes.FRIEND);
    lois.createRelationshipTo(brice, RelTypes.FRIEND);
    tx.success();
} catch (Exception e) {
    tx.failure();
} finally {
    tx.finish();
}

Nous allons maintenant créer la traversée qui permettra de connaitre les amis de Martin qui ont au moins deux amis en commun avec lui. Mais avant il faut récupérer le nœud grâce à l’index :

Node martin = users.get("name", "Martin").getSingle();

Ensuite la traversée partira de Martin et pour chaque nœud traversé nous allons récupérer ceux qui ont au moins deux relations sortantes de type Friend. Pour effectuer cette opération il faut créer un objet de type Traverser récupéré par le nœud de départ qui est Martin :

Node martin = users.get("name", "Martin").getSingle();
Traverser traverser = martin.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator() {
    @Override
        public boolean isReturnableNode(TraversalPosition traversalPosition) {
            Iterable<Relationship> it = traversalPosition.currentNode().getRelationships(Direction.OUTGOING, RelTypes.FRIEND);
            int i = 0;
            for (Relationship relationship : it) {
                i++;
            }
            return !traversalPosition.isStartNode() && i >= 2;
        }
    }, RelTypes.FRIEND, Direction.OUTGOING);
for (Node node : traverser) {
    System.out.println(node.getProperty("name"));
}

Les paramètres de la méthode traverse sont :

  • Traverser.Order : Ce paramètre peut prendre deux valeurs : soit Order. BREADTH_FIRST qui force en premier la traversée de chaque relation du nœud courant ou Order.DEPTH_FIRST qui force d’abords la traversée des nœuds enfant du nœud courant.
  • StopEvaluator : Ce paramètre définit la portée de la traversée dans le graphe. Ici dans l’exemple nous traversons tout le graphe.
  • ReturnableEvaluator : Ce paramètre prend un objet de type ReturnableEvaluator qui contient une méthode qui définit si le nœud traversé doit être récupéré. Dans l’exemple les nœuds validés doivent avoir au moins deux relations sortantes et on exclut le nœud de départ.
  • RelationshipType : Ce paramètre définit le type de relation à parcourir.
  • Direction : Ce paramètre définit la direction des relations à parcourir.

Le résultat de la traversée est :

Romain
Matthieu

Vous pouvez remarquer qu’il est très simple de faire des requêtes dans ce graph grâce à Neo4j. Les requêtes plus complexes peuvent être effectuées avec le module Cypher qui propose plus d’options.

Conclusion

La base de données Neo4j se démarque de ses concurrents grâce à sa simplicité d’utilisation, ses modules et sa communauté. Pour l’instant peu utilisée en entreprise, les bases graphe permettent de résoudre des problématiques plutôt spécifiques. Mais couplée avec une base de données relationnelle la représentation des données dans les applications est bien plus simplifiée.

De plus, le framework Java le plus connu, Spring, intègre récemment dans sa déclinaison Spring Data, le support de la base de données Neo4j. Il permet de mapper de façon avancé nos entités en graphe de données et supporte la version server de Neo4j via l’API Rest. Je vous en reparlerai dans un prochain article.

Annexe

Voici le code source complet de l'exemple : source.


Commentaires

1. Le lundi 27 février 2012, 14:05 par Christophe Furmaniak

Avez-vous pu jeter un oeil sur ce qui est proposé pour visualiser les graphes produits?

2. Le jeudi 1 mars 2012, 11:13 par Antoine ROUAZE

Il en existe plusieurs :

- Neoeclipse qui est un plugin pour eclipse.
- La console d'administration du serveur en mode standelone.
- Gephi avec le plugin Neo4j.
3. Le jeudi 31 janvier 2013, 12:20 par Painkiller

je te remercie pour ce superbe tuto , si tu peux nous faire d'autre avec Php , Neo4j for php , merci

Fil des commentaires de ce billet

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.