Hot questions for Using Neo4j in spring data neo4j 5

Top Java Programmings / Neo4j / spring data neo4j 5

Question:

I am currently working on a Spring Data Neo4j 5.0.3 REST API application that interfaces with a Neo4j 3.3.1 causal cluster consisting of 3 core nodes (1 leader and 2 followers). For better or for worse, we are also submitting a lot of custom cypher queries to the database using session.query a la SQL prepared statements. When we do this, we notice that virtually none of our custom Cypher submitted via session.query gets sent to any of the read-only followers.

I've cracked into the code and noticed that within Neo4jSession, the query method always creates a transaction with type READ_WRITE. Is there any way to get around this so that our queries get distributed across our cluster correctly?

I have also tried marking the appropriate methods with @Transactional(readOnly = true) but it doesn't seem to work. When I did into Neo4jTransactionManager, I see the following around line 218:

private Transaction.Type getTransactionType(TransactionDefinition definition, Neo4jTransactionObject txObject) {
    Transaction.Type type;
    if (definition.isReadOnly() && txObject.isNewSessionHolder()) {
        type = Transaction.Type.READ_ONLY;
    } else if (txObject.transactionData != null) {
        type = txObject.transactionData.type();
    } else {
        type = Transaction.Type.READ_WRITE;
    }
    return type;
}

What does that second condition, isNewSessionHolder, in the first branch mean? Within the context of a single HTTP API call, we call out to the database at least 2 times, so by the second query, I believe this condition always returns false.

Given these observations, are there any easy ways forward for making my queries be respected as read-only?


Answer:

First part regarding Spring: Due to limitations of Spring AOP, it is not possible to have multiple isolated transactions within one class. The best solution would be to separate the calling code from the transactional methods in different classes. Then the @Transactional(readOnly = true) will work.

Second part regarding OGM's session.query calls: If your unit of work participates in an existing READ_WRITE transaction, e.g. this happens because of the @Transactional AOP problem above, there is no way to set the type to READ_ONLY. As a default OGM will always create a READ_WRITE transaction if no explicit type is set.

tl;dr;

There are two solutions for the problem in general:

  1. Extract the @Transactional methods into another class and leave the caller code in the existing one.
  2. Create the Session object manually by injecting the SessionFactory and create a transaction with READ_ONLY type. (and remove the @Transactional annotation)

(as answered in the Neo4j user slack)

Question:

To keep in line with DDD and Bounded Contexts, its well known that when you create your microservices you should keep separation of concerns.

One of the main benefits of Neo4J is keeping your "connected" data in Neo4J so relationships between them are efficiently queried.

These two opposing forces seem to make an microservice architecture decision difficult when choosing to use Neo4J.

Do you have multiple microservices connect to Neo4J db and persist their own domain accordingly?

OR

Do you have one microservice with a db connection to Neo4J that controls persistance and querying?

Both dont seem quite right...


Answer:

The pattern of database-per-service is discussed here, and breaks down the options to:

  1. Shared database, but private tables per service.
  2. Shared database, but private schema per service.
  3. Separate database per service.

Obviously 3 is going to be the most expensive, as you would want each Neo4j instance to be on its own server so it has dedicated memory and hardware, and if you need a clustering solution then this becomes a separate cluster-per-service. Not recommended, especially if just ideology is the driver for this decision.

1 and 2 are better options, especially if the data accessed across microservices is related intrinsically, as Neo4j works best when storing connected data, and loses its value the more the data is siloed between multiple databases.

That said, there are some challenges with these options.

Neo4j does not use tables, and does not currently have separate schema to divide visibility of data between different users.

While you can have the microservice only use defined queries that only touch specific parts of the graph, this is often looser control than is required.

You can instead use the subgraph access control approach, and this is the one I would most recommend for your needs.

This boils down to creating procedures that encapsulate the queries you want to be available for each microservice (that either use the Java API directly, or make a Cypher query from the procedure code), and then creating custom roles for each microservice (without read permissions), but allowing them to invoke the appropriate procedures. This ensures that the custom role per microservice is only allowed to interact with the graph through the allowed procedures (and the procedures of course can do whatever they want without being constrained by the roles or permissions of the calling user).

As far as a multi-tenancy approach, keeping data separate between different graphs on a single database, that is a feature of high interest right now, and implementation is in the works. Look for this in upcoming releases in 2018. That said, whether this would be useful to you or not depends upon the implementation of this feature, and the nature of your data.

Question:

Setting

I import a class from my Java project via

import myproj.domain.ActionResponse;

Then I try to make an interface for a repository with by extending a Neo4jRepository.

I am following these docs: "parameter types change from <T> to <T, ID>" - https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/

@Repository
    public interface ActionResponseRepository extends Neo4jRepository<ActionResponse, ActionResponse.getId() >  {
...

ActionResponse extends NamedType which extends GraphType which has a

...
@GraphId
    @JsonProperty("id")
    Long id;

    public Long getId() {
        return id;
    }
...

Question

This: extends Neo4jRepository<ActionResponse, ActionResponse.getId() > is incorrect syntax.

How do I fill the second parameter field with the id from the ActionReponse class?


Answer:

The second parameter of the annotation is the ID type.

So you should declare something like:

extends Neo4jRepository<ActionResponse, Long>