DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 2: Understanding Neo4j
  • Spring Data Neo4j: How to Update an Entity
  • Leveraging Neo4j for Effective Identity Access Management
  • The Beginner's Guide To Understanding Graph Databases

Trending

  • The Hidden Latency of Autoscaling
  • Practical Coding Principles for Sustainable Development
  • Bringing Intelligence Closer to the Source: Why Real-Time Processing is the Heart of Edge AI
  • Introduction to Retrieval Augmented Generation (RAG)
  1. DZone
  2. Data Engineering
  3. Databases
  4. Adding gRPC to Neo4j

Adding gRPC to Neo4j

Between REST APIs and GraphQL, gRPC seems to be left out sometimes. In this post, we take a look at creating a gRPC server on top of Neo4j!

By 
Max De Marzi user avatar
Max De Marzi
·
Dec. 15, 17 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
5.0K Views

Join the DZone community and get the full member experience.

Join For Free

You are probably sick of me saying it, but one of the things I love about Neo4j is that you can customize it any way you want. Extensions, stored procedures, plugins, custom indexes, custom apis, etc. If you want to do it, then you can do it with Neo4j.

So, the other day, I was like, what about this gRPC thing? Many companies standardize their backend using RESTful APIs, others are trying out GraphQL, and some are using gRPC. Neo4j doesn’t support gRPC out-of-the-box, partially because we have our own custom binary protocol “Bolt,” but we can add a rudimentary version of gRPC support quite easily.

Let's build a Neo4j Kernel Extension to handle sending and receiving Cypher queries via gRPC. We’ll need to add a few new dependencies to our pom.xml file to pull in the gRPC libraries.

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty</artifactId>
    <version>${grpc.version}</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>${grpc.version}</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>${grpc.version}</version>
</dependency>

With those in place, let’s start by building our neo4j.proto file. Our Service will simply execute a Cypher query string and return a Cypher query result. Both are just strings, but we could map them properly if we really wanted — but we are keeping it simple for now.

syntax = "proto3";
 
option java_multiple_files = true;
option java_package = "com.maxdemarzi";
option java_outer_classname = "Neo4jGRPCProto";
 
service Neo4jQuery {
    rpc ExecuteQuery (CypherQueryString) returns (stream CypherQueryResult) { }
}
 
message CypherQueryString {
    string query = 1;
}
 
message CypherQueryResult {
    string result = 1;
}

Once this file exists, we can use Maven to run protobuf:compile and it will automatically generate some files for us.

I believe there are all the files required for us to build a gRPC Client to talk to our service… which we haven’t created yet. To build our server, we need to use Maven again and run protobuf:compile-custom, which creates a Neo4jQueryGrpc file with a Neo4jQueryImplBase class we need to extend. All this magically generated code makes me dizzy (or it could be the pain pills).

Anyway, we’ll create a constructor for it passing it the database, which we will call when we register our extension a little later.

public class Neo4jGRPCService extends Neo4jQueryGrpc.Neo4jQueryImplBase {
    private static GraphDatabaseService db;
 
    Neo4jGRPCService(GraphDatabaseService db) {
        Neo4jGRPCService.db = db;
    }

The real work happens in the executeQuery method which begins a transaction, executes the Cypher query and streams the results back. Yes, that stringObjectMap.toString() is a bit lazy, but just go along with me for now.

@Override
public void executeQuery(CypherQueryString req, StreamObserver<CypherQueryResult> responseObserver) {
    try (Transaction tx = db.beginTx()) {
        Result result = db.execute(req.getQuery());
        result.stream().forEach(stringObjectMap -> {
            CypherQueryResult r = CypherQueryResult.newBuilder().setResult(stringObjectMap.toString()).build();
            responseObserver.onNext(r);
        });
        tx.success();
    }
    responseObserver.onCompleted();
} 

Now, we need to create a KernelExtensionFactory that registers our gRPC extension and calls that constructor we created earlier.

public class RegistergRPCExtensionFactory extends KernelExtensionFactory<RegistergRPCExtensionFactory.Dependencies> {
 
    @Override
    public Lifecycle newInstance(KernelContext kernelContext, final Dependencies dependencies) throws Throwable {
        return new LifecycleAdapter() {

When we start our Neo4j instance, we will create a gRPC server on port 9999 and add the Neo4jGRPCService we created passing in the database service.

public class RegistergRPCExtensionFactory extends KernelExtensionFactory<RegistergRPCExtensionFactory.Dependencies> {
 
    @Override
    public Lifecycle newInstance(KernelContext kernelContext, final Dependencies dependencies) throws Throwable {
        return new LifecycleAdapter() {

Now let’s see if any of this works by creating a test:

public class Neo4jGRPCServiceTest {
 
    @Rule
    public final Neo4jRule neo4j = new Neo4jRule()
            .withFixture(MODEL_STATEMENT);

We’ll use the @Rule to get a Neo4j Service started, and have it start with a single Person node already created with the name of max. We will query for this node later.

private static final String QUERY = "MATCH (n:Person) WHERE n.name = 'max' RETURN n.name";
 
    private static final String MODEL_STATEMENT =
            "CREATE (n:Person {name:'max'})";  

We’ll also need to create a blocking Stub and set up a channel between our test and our server:

private static Neo4jQueryGrpc.Neo4jQueryBlockingStub blockingStub;
 
@Before
public void setup() throws Exception {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9999)
            // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
            // needing certificates.
            .usePlaintext(true)
            .build();
    blockingStub = Neo4jQueryGrpc.newBlockingStub(channel);
}

Once all that is in place, we can actually write our test. We create a CypherQueryString using the QUERY we defined earlier. Then, call executeQuery from our Stub and check to see if the result matches our expectations. A bunch of these classes were generated for us earlier which makes life easy. One of the benefits of gRPC is that it can generate the client and server files in multiple languages. So once you have the .proto file, you are off to the races.

@Test
public void testQuery() throws Exception {
 
    CypherQueryString queryString = CypherQueryString.newBuilder().setQuery(QUERY).build();
    CypherQueryResult response;
 
    Iterator<CypherQueryResult> iterator = blockingStub.executeQuery(queryString);
    while (iterator.hasNext()) {
        response = iterator.next();
        Assert.assertEquals("{n.name=max}", response.getResult());
    }
} 

Which, of course it does! …and there you have it. A few lines of code, a couple of hours of reading the gRPC documentation and looking wearily at the magically autogenerated code, and we have a rudimentary gRPC extension on Neo4j. The code, as always, is on GitHub and if your organization is using gRPC in the back-end and wants to help build a proper gRPC extension, please get in touch and let’s work together on it.

If zeromq and message pack are more your thing, Michael Hunger was messing around with one of those a while back.

Neo4j

Published at DZone with permission of Max De Marzi. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 2: Understanding Neo4j
  • Spring Data Neo4j: How to Update an Entity
  • Leveraging Neo4j for Effective Identity Access Management
  • The Beginner's Guide To Understanding Graph Databases

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook