Neo4j

Neo4j est une base de données nosql orientée graphe. Qu'est-ce-que ça veut dire ? Une base classique de type SQL impose de structurer sa base en tables contenant des champs typés. Il faut donc savoir dès le début de son projet ce qu'on veut stocker précisément. Ensuite, on crée des relations entre ces tables à l'aide de jointures afin de récupérer des données complexes. Si j'ai besoin de vous en expliquer plus à ce sujet, c'est que vous n'êtes pas sur le bon blog, désolé :x

Neo4j n'a pas de table. C'est un gros panier dans lequel on crée des nœuds. Ces nœuds contiennent des données libres et on peut relier certains nœuds entre eux. Et ça donne des représentations graphiques de ce genre :

Neo4j

Pour comprendre la puissance de Neo4j, nous allons prendre un cas pratique et l'étudier pour une base de données SQL, puis pour Neo4j. Nous allons prendre un système contenant des User qui sont reliés à des Blog par une relation de type OWN et qui suivent d'autres Blog par une relation de type FOLLOW. Puis, nous récupèrerons tous les User qui FOLLOW les Blog appartenant (OWN) à l'utilisateur d'id 1.

SQL

La première chose à faire est de créer la structure de sa base de données. Nous allons donc créer une table User, une table Blog, une table blogownedby et une table blogfollowedby :

CREATE TABLE user
(
    id      SERIAL NOT NULL PRIMARY KEY,
    name    TEXT NOT NULL
);
CREATE TABLE blog
(
    id              SERIAL NOT NULL PRIMARY KEY,
    title           TEXT NOT NULL,
    description     TEXT NOT NULL
);
CREATE TABLE blog_owned_by
(
    id_user     INT NOT NULL,
    id_blog     INT NOT NULL
);
CREATE TABLE blog_followed_by
(
    id_user     INT NOT NULL,
    id_blog     INT NOT NULL
);

Une fois ces tables crées, nous pouvons alors insérer en base de nouveaux utilisateurs, leur ajouter des blogs, puis leur faire follower d'autres blogs :

# On cree trois utilisateurs : Fernand, Paul, et Sami et son premier blog
INSERT INTO user (name) VALUES("Fernand");
INSERT INTO user (name) VALUES("Paul");
INSERT INTO user (name) VALUES("Sami");

# Pour chacun, on va creer des blogs :
INSERT INTO blog (title) VALUES("blog #1 de Fernand");
INSERT INTO blog (title) VALUES("blog #2 de Fernand");
INSERT INTO blog (title) VALUES("blog #1 de Paul");
INSERT INTO blog (title) VALUES("blog #1 de Sami");
INSERT INTO blog (title) VALUES("blog #2 de Sami");
INSERT INTO blog (title) VALUES("blog #3 de Sami");

# On cree ensuite des relations entre ces blogs et ces users. Commençons par les relations de type blog_owned_by
INSERT INTO blog_owned_by VALUES(1, 1); #user 1, blog 1
INSERT INTO blog_owned_by VALUES(1, 2);
INSERT INTO blog_owned_by VALUES(2, 3);
INSERT INTO blog_owned_by VALUES(3, 4);
INSERT INTO blog_owned_by VALUES(3, 5);
INSERT INTO blog_owned_by VALUES(3, 6);

# Et pour finir, nous creons des relations de type blog_followed_by :
INSERT INTO blog_followed_by VALUES(1, 3); #user 1 suit blog 3
INSERT INTO blog_followed_by VALUES(1, 4);
INSERT INTO blog_followed_by VALUES(1, 5);
INSERT INTO blog_followed_by VALUES(2, 1);
INSERT INTO blog_followed_by VALUES(2, 6);
INSERT INTO blog_followed_by VALUES(3, 1);
INSERT INTO blog_followed_by VALUES(3, 2);

Voilà ! Nous avons dans notre base deux utilisateurs et six blogs reliés entre eux. On peut maintenant faire notre requête pour récupérer tous les utilisateurs qui follow tous les blogs de Fernand (Merci à Torgan dont le SQL est la langue maternelle et qui m'a aidé à rédiger la requête) :

SELECT follower.name
FROM   "user" u
INNER JOIN blog_owned_by bob
    ON bob.id_user = u.id
INNER JOIN blog b
    ON b.id = bob.id_blog
INNER JOIN blog_followed_by bfb
    ON bfb.id_blog = b.id
INNER JOIN "user" follower
    ON follower.id = bfb.id_user
WHERE u.name = 'Fernand';

Ce qu'on constate, c'est que c'est peu clair, et que c'est extrêmement lourd. Ne parlons pas de la requête en elle même sur de grosse bases de données et avec des relations bien plus complexes.

Neo4j

Passons à Neo4j. C'est vraiment très différent. Neo4j propose un langage bien spécifique nommé Cypher. C'est dans le même esprit que SQL mais tellement plus clair de par son aspect très graphique. Ici, on dessine ses requêtes. Allons y.

Alors la première chose qui change, c'est qu'on n'a plus la phase de structuration de la base. On passe directement à la phase d'insertion de données. Fou non ?

CREATE
    (u1:User {name: "Fernand"}),
    (u2:User {name: "Paul"}),
    (u3:User {name: "Sami"}),
    (b1:Blog {title: "blog #1 de Fernand"}),
    (b2:Blog {title: "blog #2 de Fernand"}),
    (b3:Blog {title: "blog #1 de Paul"}),
    (b4:Blog {title: "blog #1 de Sami"}),
    (b5:Blog {title: "blog #2 de Sami"}),
    (b6:Blog {title: "blog #3 de Sami"}),
    (u1)-[:OWN]->(b1),
    (u1)-[:OWN]->(b2),
    (u2)-[:OWN]->(b3),
    (u3)-[:OWN]->(b4),
    (u3)-[:OWN]->(b5),
    (u3)-[:OWN]->(b6),
    (u1)-[:FOLLOW]->(b3),
    (u1)-[:FOLLOW]->(b4),
    (u1)-[:FOLLOW]->(b5),
    (u2)-[:FOLLOW]->(b1),
    (u2)-[:FOLLOW]->(b6),
    (u3)-[:FOLLOW]->(b1),
    (u3)-[:FOLLOW]->(b2)
RETURN *

Neo4j

Et voilà le résultat ! En une seule requête, nous avons inséré toutes les données en base et nous les récupérons. Pour les récupérer par la suite, nous utiliserons cette requête :

MATCH (u:User)-[:FOLLOW]->(b:Blog)<-[:OWN]-(u1:User) WHERE u1.name="Fernand" RETURN DISTINCT(u.name);

# Resultat :
# Sami
# Paul

N'est-ce-pas clairement plus simple à lire et à écrire ?? Mais si ! Et en plus, les perfs sont incroyablement meilleures que pour du SQL.

Je pourrais aussi rajouter que Neo4j est fourni avec un webadmin permettant de voir le résultat de ses requêtes Cypher graphiquement dans une magnifique interface et qu'elle dispose d'une API REST permettant de l'interroger depuis n'importe quel type de backoffice. J'espère que je vous aurais donné envie d'y gouter !

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu