Devoxx 2013 FR : retour d'expérience

La conférence Devoxx 2013 à Paris s'est ouverte sur une présentation de Neo4j, une base de donnée orientée Graphes. Retour sur les principales fonctionnalités de ce puissant outil.

Création du graphe 

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 inclut 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

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 Nœuds 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é lors de la 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 certain nombre d'aspects sur lesquels le 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.