Devoxx 2013 FR : (REALITY)-[:IS_A]->(GRAPH)

La première journée de conférences Devoxx 2013 à Paris s'est ouverte sur une présentation de Neo4j par Florent Biville, une base de donnée orientée Graphes. A l'issue de cette conférence il me semble intéressant de retranscrire les principales fonctionnalités de ce puissant outil.

logo-Neo4j.jpg

Création de graphe Neo4j

Tout commence par l'import ou la création du graphe en y ajoutant nœuds, relations entre ces nœuds et propriétés sur ces nœuds et relations. Neo4j fourni pour ça une API java de type DSL relativement intuitive. Soulignons que toutes les opérations de modification du graphe s'effectuent en mode transactionnel ce qui n'est pas toujours le cas en NoSQL.

Requêtage

Pour effectuer une recherche il faut d'abord sélectionner un point d'entrée sur le graphe puis décrire le pattern recherché. Par exemple, rechercher toutes les personnes qui ont une relation de type "est ami" avec le point d'entrée sélectionné, ce qui donne par exemple :

private static Traverser getAmisDe(final Node personne) {
  TraversalDescription td = Traversal.description()
    .breadthFirst() // breadthFirst() pour recherche en largeur
                    // sinon depthFirst() pour une rech. en profondeur
    .relationships(TypeRelation.EST_AMI, Direction.BOTH )
    .evaluator(Evaluators.excludeStartPosition());
  return td.traverse(personne);
}

Sachez que par défaut, l'algorithme de sélection du point d'entrée repose sur Lucene, ce qui vous garantit de bonnes performances.

En complément des critères de sélection habituels tels que l'égalité ou l'inclusion, Neo4j inclus des algorithmes de recherche bien connus de la théorie des graphes. Le plus court chemin, Dijkstra ou encore A* pourront ainsi être employés pour le calcul d'itinéraires.

Cypher : le SQL des graphes

Neo4j propose également un langage nommé Cypher qui permet d'écrire vos requêtes de manière déclarative comme en SQL, par exemple :

START personne = node:node_auto_index(prenom ='Stéphane')
MATCH (personne)-[:EST_AMI]-(ami)
RETURN ami

L'avantage d'un tel langage est de pouvoir modifier puis tester plus facilement vos requêtes. Cette console Neo4j en est une bonne illustration : http://console.neo4j.org

Services REST

Neo4j propose également une API REST complète permettant de parcourir et de manipuler l'ensemble du graphe. L'API fonctionne en mode "exploration" c'est à dire que chaque réponse REST décrit comment manipuler l'élément et comment explorer le reste du graphe, un peu à la manière d'ElascticSearch.

{
 "paged_traverse" : "http://localhost:7474/db/data/node/1/paged/traverse/{?pageSize,leaseTime}",
 "outgoing_relationships" : "http://localhost:7474/db/data/node/1/relationships/out",
''[...]''
 "create_relationship" : "http://localhost:7474/db/data/node/1/relationships",
  "data" : {"prenom":"Stéphane"}
}

Bien que cette API REST soit utile d'un point de vue technique ou pour faire du B2B elle n'est pas adaptée à un usage fonctionnel car trop bas niveau.

Il est davantage souhaitable de disposer de services permettant de retrouver en un seul appel REST tous les amis d'une personne donnée plutôt que d'effectuer une série d'appels basés sur les identifiant techniques de nœuds.

Pour exposer des services REST ayant une approche plus fonctionnelle Neo4j propose un module nommé "Unmanaged extension API".

Spring Data Neo4j : la cerise sur le gâteau

logo-spring-data-neo4j.png

Spring Data Neo4j est une surcouche qui propose une approche plus classique orientée POJOs, annotations et Repository (DAO).

Voici un exemple de modélisation de Pojos sous forme de Noeuds avec Spring Data :

// @NodeEntity transforme le Pojo en Noeud du graphe
@NodeEntity
public class Personne {
    // @GraphId id du noeud. A usage technique exclusivement, pas fonctionnel!!!
    @GraphId
    private Long id;
 
    // @Indexed permet la recherche du noeud via ce champ
    @Indexed
    private String prenom;
 
    // @RelatedTo exprime une relation entre deux entités
    @RelatedTo(type = "est_ami", direction = Direction.BOTH)
    private Set<Personne> amis;
}

Une fois l'ensemble de Nœuds du graphe mappés sous forme de Pojos, il ne reste plus qu'à déclarer des interfaces de type Repository pour pouvoir accéder et manipuler ces nœuds :

public interface PersonneRepository extends GraphRepository<Personne> {
  // Implémentation par convention de nommage sur l'attribut prénom      List<Personne> findByPrenomLike(String name);
 
  // Implémentation par interprétation d'une requête Cypher   
  @Query("start personne=node:Personne(prenom=) match (personne)-[:EST_AMI]-(ami) return ami")   
  List<Personne> getAmis(String prenom);
 
  // La même requête avec pagination des résultats   
  @Query("start personne=node:Personne(prenom=) match (personne)-[:EST_AMI]-(ami) return ami")
  Page<Personne> getAmis(String prenom, PageRequest pageRequest); 
}

Il n'y a pas d'implémentation à écrire car Spring Data se charge de générer à votre place une implémentation à la volée en procédant par inspection de l'interface.

Conclusion

Comme expliqué par Florent Biville lors de sa présentation, d'une certaine manière tout peut être représenté sous forme de graphe et finalement une base de donnée relationnelle n'est ni plus ni moins qu'un graphe :).

Soit, mais au delà des fonctionnalités mises en avant il reste un certains nombre d'aspects sur lesquels monde SQL a beaucoup d'avance comme par exemple en terme de visualisation des données, de migration de données ou encore de monitoring.

Il n'empêche, Neo4j est une réelle option à étudier lorsqu’il s'agit de détecter des "patterns" sur des graphes ayant une topologie complexe : réseaux (sociaux et autres), calculs d'itinéraires, de flux etc.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.