Spring-data neo4j 4.0 release multi-tenant

Hi all, I'm new to neo4j and just integrating neo4j with spring data to our web application where the back-end is written in java. We already use spring, hibernate and ms-sql. Also use the multi-tenant as tenant per database. So every tenant has it own database.

We now integrate neo4j with spring-data and want also have a neo4j instance(connection) per tenant.

For now I did some nasty hacky workaround by creating a own classes "class MultiTenantNeo4jSessionFactory extends SessionFactory" that holds the connection per tenant and gives back the right SessionFactory for a "class MultiTenantNeo4jTransactionManager extends AbstractPlatformTransactionManager", which is the same as the Neo4jTransacionManger but uses the MultiTenantNeo4jSessionFactory to get the right Neo4jSessionFactory.

This is hacky because I also needed to copy some private classes that not supposed to be copied at all. Like "class SessionTenantHolder extends SessionHolder" that is used by Neo4jTransactionObject and also used in SessionFactoryUtils where it is cast.

Above not the case anymore, after some investigation I only needed a own "class MultiTenantNeo4jSessionFactory extends SessionFactory". That can be given to the Neo4jTransactionManager Like below:

package my.company.neo4j.configuration;

import java.util.HashMap;
import java.util.Map;

import org.neo4j.ogm.config.Configuration;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;

import my.company.tenant.TenantContextHolder;
import my.company.tenant.TenantInitializer;

public class MultiTenantNeo4jSessionFactory extends SessionFactory implements TenantInitializer {
	private Map<String, SessionFactory> m_tenantSessionFactories = new HashMap<>();
	private String[] m_packages;

	public MultiTenantNeo4jSessionFactory(Configuration configuration, String masterPackage, String... packages) {
		super(configuration, masterPackege);
		m_packages = packages;
	}

	@Override
	public Session openSession() {
		String tenantLabel = TenantContextHolder.getContext().getTenantLabel();
		return m_tenantSessionFactories.get(tenantLabel).openSession();
	}

	@Override
	public void close() {
		String tenantLabel = TenantContextHolder.getContext().getTenantLabel();
		m_tenantSessionFactories.get(tenantLabel).close();
	}

	@Override
	public void initialize() {
		String tenantLabel = TenantContextHolder.getContext().getTenantLabel();
		if (tenantLabel.equals("tenant1")) {
			m_tenantSessionFactories.put(tenantLabel, new SessionFactory(new org.neo4j.ogm.config.Configuration.Builder().uri("bolt://localhost:7681").credentials("neo4j", "test").build(), m_packages));
		} else if (tenantLabel.equals("tenant2")) {
			m_tenantSessionFactories.put(tenantLabel, new SessionFactory(new org.neo4j.ogm.config.Configuration.Builder().uri("bolt://localhost:7682").credentials("neo4j", "test").build(), m_packages));
		}
	}

	SessionFactory getSessionFactory() {
		String tenantLabel = TenantContextHolder.getContext().getTenantLabel();
		return m_tenantSessionFactories.get(tenantLabel);
	}

	@Override
	public void destroy() {
		m_tenantSessionFactories.values().forEach(session -> session.close());
	}
}

//...//
package my.company.neo4j.configuration;

import org.neo4j.ogm.session.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;

@Configuration
@EnableNeo4jRepositories(basePackages = "my.company.neo4j", sessionFactoryRef = "neo4jSessionFactory", transactionManagerRef = "neo4JtransactionManager")
public class Neo4jEducatorConfiguration {
	
	@Bean
	public SessionFactory neo4jSessionFactory() {

		org.neo4j.ogm.config.Configuration masterConfiguration = new org.neo4j.ogm.config.Configuration.Builder().uri("bolt://localhost:7687").credentials("neo4j", "test").build();
		return new MultiTenantNeo4jSessionFactory(masterConfiguration, "my.company.neo4j.master", "my.company.neo4j");
	}
	
	@Bean
	public Neo4jTransactionManager neo4JtransactionManager() {
		return new Neo4jTransactionManager(neo4jSessionFactory());
	}
}

TenantContextHolder and TenantInitializer are own code, but you guys get the point.

So it works, but we are not sure if this is the way to go. So that leaves me with questions like:

  • I saw that in neo4j 4.0 there is some progress for this, but is this also available for community edition?
  • And also is there a roadmap when a spring-data version is released that use the neo4j-ogm 4.0?
  • Somebody knows another way to implement this nicely without using Label per Tenant?
  • Why?
    • Because I believe in strictly separated storage and not convinced yet that everything is handled by adding Tenant per label.
    • Also we want to use the same id for neo4j as the primary key of mssql, so that means that nodes with the same id can exists for different tenants.
    • And if you use the browser you need to add a where statement every time you query some results?

Here's a link to some of the most recent discussions on multi-tenancy Multi-Tenancy on Neo4j. There's a lot of good info here about ways to implement multi-tenancy and Neo4j's road map.

Tnx, already scanned that post but did not get a full understanding what was possible at neo4j 3.x and 4.0, but found the document Neo4j_EE_4.0_MR2_Doc.pdf in that post helpful to see a glimpse of the future.

Nevertheless I found my solution in creating a MultiTenantNeo4jSessionFactory to serve SessionFactory per Tenant. I will take a look at this again when upgrading to 4.0 and consider then if this approach is still valid.