Déploiement de Neo4j avec Docker et Intégration Spring Boot via Spring Data

Les bases de données orientées graphe excellent dans la modélisation et l'interrogation de réseaux complexes. Contrairement aux systèmes relationnels traditionnels, elles stockent les relations au même niveau que les nœuds, offrant des performances optimales pour les requêtes de parcours, telles que la recherche du chemin le plus court. Neo4j est l'une des solutions les plus abouties dans ce domaine. Bien qu'il existe des alternatives comme Nebula Graph ou ArangoDB, Neo4j reste une référence grâce à son langage de requête déclaratif, Cypher.

Déploiement de Neo4j avec Docker

Pour un environnement de développement ou de test, l'édition communautaire de Neo4j déployée via Docker est une approche rapide et efficace. Voici une configuraton docker-compose.yml structurée pour isoler les données et configurer les ressources mémoire :

version: '3.8'
services:
  graph-database:
    image: neo4j:5.15-community
    container_name: neo4j-instance
    restart: unless-stopped
    ports:
      - "7475:7474"
      - "7688:7687"
    environment:
      - NEO4J_AUTH=neo4j/SecureP@ssw0rd!
      - NEO4J_server_memory_heap_initial__size=256M
      - NEO4J_server_memory_heap_max__size=512M
      - NEO4J_server_memory_pagecache_size=256M
      - NEO4J_server_directories_import=/data/import
    volumes:
      - ./neo4j_data:/data
      - ./neo4j_logs:/logs
      - ./neo4j_plugins:/plugins

Exécutez la commande docker compose up -d pour initialiser le conteneur. L'interface d'administration web sera ensuite accessible via http://localhost:7475. Vous pouvez vérifier la version du moteur en exécutant la requête Cypher suivante dans le navigateur intégré :

CALL dbms.components() YIELD name, versions, edition 
RETURN name, versions, edition

Modélisation du graphe et données d'exemple

Pour démontrer les capacités d'interroagtion, nous allons modéliser un réseau reliant des développeurs à des projets logiciels. Les nœuds seront Developer et Software, reliés par des relations CONTRIBUTED_TO (incluant des rôles spécifiques) et ARCHITECTED.

Exécutez le script Cypher suivant pour peupler la base de données :

CREATE (linux:Software {title: 'Linux Kernel', released: 1991, description: 'Unix-like OS kernel'})
CREATE (python:Software {title: 'Python', released: 1991, description: 'High-level programming language'})
CREATE (java:Software {title: 'Java', released: 1995, description: 'Object-oriented language'})
CREATE (cpp:Software {title: 'C++', released: 1985, description: 'General-purpose programming language'})

CREATE (linus:Developer {name: 'Linus Torvalds', born: 1969})
CREATE (guido:Developer {name: 'Guido van Rossum', born: 1956})
CREATE (james:Developer {name: 'James Gosling', born: 1955})
CREATE (bjarne:Developer {name: 'Bjarne Stroustrup', born: 1950})
CREATE (andrew:Developer {name: 'Andrew Tanenbaum', born: 1944})

CREATE
(linus)-[:CONTRIBUTED_TO {roles: ['Creator', 'Lead Maintainer']}]->(linux),
(andrew)-[:CONTRIBUTED_TO {roles: ['Reviewer', 'Inspiration']}]->(linux),
(guido)-[:CONTRIBUTED_TO {roles: ['Creator', 'BDFL']}]->(python),
(james)-[:ARCHITECTED]->(java),
(james)-[:CONTRIBUTED_TO {roles: ['Original Author']}]->(java),
(bjarne)-[:ARCHITECTED]->(cpp),
(bjarne)-[:CONTRIBUTED_TO {roles: ['Designer']}]->(cpp);

Intégration Spring Boot avec Spring Data Neo4j

Depuis Spring Boot 2.4, Spring Data Neo4j (SDN) a été entièrement réécrit pour s'aligner sur les standards modernes de Spring Data. Nous utiliserons ici une approche basée sur SDN 6+ avec les annotations @Node et @RelationshipProperties.

Configuration des dépendances Maven (pom.xml) :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>graph-integration</artifactId>
    <version>1.0.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Configuration de la connexion dans application.yml :

spring:
  neo4j:
    uri: bolt://localhost:7688
    authentication:
      username: neo4j
      password: SecureP@ssw0rd!

Définition des entités

Les classes d'entité mappent directement les nœuds et les relations riches du graphe.

package com.example.graph.entity;

import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;

@Node("Software")
public class Software {

    @Id
    private String title;
    private Integer released;
    private String description;

    // Constructeurs, getters et setters omis pour la concision
}
package com.example.graph.entity;

import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;

import java.util.List;

@Node("Developer")
public class Developer {

    @Id
    private String name;
    private Integer born;

    @Relationship(type = "CONTRIBUTED_TO")
    private List<Contribution> contributions;

    @Relationship(type = "ARCHITECTED")
    private List<Software> architectedProjects;

    // Constructeurs, getters et setters omis pour la concision
}
package com.example.graph.entity;

import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
import org.springframework.data.neo4j.core.schema.TargetNode;

import java.util.List;

@RelationshipProperties
public class Contribution {

    @Id
    @GeneratedValue
    private Long id;

    private List<String> roles;

    @TargetNode
    private Software software;

    // Constructeurs, getters et setters omis pour la concision
}

Interfaces Repository

Les repositories permettent d'effectuer des opérations CRUD standards ainsi que des requêtes Cypher personnalisées.

package com.example.graph.repository;

import com.example.graph.entity.Software;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface SoftwareRepository extends Neo4jRepository<Software, String> {

    @Query("MATCH (d:Developer {name: $devName})-[:CONTRIBUTED_TO]->(s:Software) RETURN s")
    List<Software> findSoftwareByContributor(String devName);
}
package com.example.graph.repository;

import com.example.graph.entity.Developer;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface DeveloperRepository extends Neo4jRepository<Developer, String> {

    @Query("MATCH (d:Developer)-[:ARCHITECTED]->(s:Software {title: $softTitle}) RETURN d")
    List<Developer> findArchitectsBySoftware(String softTitle);
}

Tests d'intégration

La classe de test suivante valide le chargement des entités et l'exécution des requêtes personnalisées.

package com.example.graph;

import com.example.graph.entity.Developer;
import com.example.graph.entity.Software;
import com.example.graph.repository.DeveloperRepository;
import com.example.graph.repository.SoftwareRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class GraphIntegrationTest {

    @Autowired
    private DeveloperRepository developerRepository;

    @Autowired
    private SoftwareRepository softwareRepository;

    @Test
    public void testFindSoftwareByContributor() {
        List<Software> projects = softwareRepository.findSoftwareByContributor("James Gosling");
        projects.forEach(software -> 
            System.out.println("Projet contribué : " + software.getTitle())
        );
    }

    @Test
    public void testFindArchitects() {
        List<Developer> architects = developerRepository.findArchitectsBySoftware("C++");
        architects.forEach(dev -> 
            System.out.println("Architecte : " + dev.getName())
        );
    }

    @Test
    public void testCreateNewNode() {
        Developer newDev = new Developer();
        newDev.setName("Ada Lovelace");
        newDev.setBorn(1815);
        developerRepository.save(newDev);
        System.out.println("Nouveau développeur enregistré.");
    }
}

Étiquettes: neo4j Spring Boot Spring Data Neo4j Docker Cypher

Publié le 25 septembre à 21h57