Making spring-data-mongodb multi-tenant
Asked Answered
R

4

14

In a post last august sbzoom proposed a solution to make spring-data-mongoDB multi-tenant:

"You have to make your own RepositoryFactoryBean. Here is the example from the Spring Data MongoDB Reference Docs. You would still have to implement your own MongoTemplate and delay or remove the ensureIndexes() call. But you would have to rewrite a few classes to make sure your MongoTemplate is called instead of Spring's."

Did anybody implement this or something equivalent?

Reprisal answered 1/5, 2013 at 20:6 Comment(0)
I
23

There's quite a few ways to skin the cat here. It essentially all boils down to on which level you'd like to apply the tenancy.

Basics

The basic approach is to bind some kind of key identifying the customer on a per-thread basis, so that you can find out about the customer the current thread of execution deals with. This is usually achieved by populating a ThreadLocal with some authentication related information as you can usually derive the tenant from the logged in user.

Now if that's in place there's a few options of where to apply the tenant knowledge. Let me briefly outline the most common ones:

Multi-tenancy on the database level

One way to separate data for multiple clients is to have individual databases per tenant. Spring Data MongoDB's core abstraction for this is the MongoDBFactory interface. The easiest way here is to override SimpleMongoDbFactory.getDb(String name) and call the parent method with the database name e.g. enriched by the tenant prefix or the like.

Multi-tenancy on the collection level

Another option is to have tenant specific collections, e.g. through tenant pre- or postfixes. This mechanism can actually be leveraged by using the Spring Expression language (SpEl) in the @Document annotation's collectionName attribute. First, expose the tenant prefix through a Spring bean:

 @Component("tenantProvider")
 public class TenantProvider {

   public String getTenantId() {
     // … implement ThreadLocal lookup here
   }
 }

Then use SpEL in your domain types @Document mapping:

 @Document(collectionName = "#{tenantProvider.getTenantId()}_accounts"
 public class Account { … }

SpEl allows you to refer to Spring beans by name and execute methods on them. MongoTemplate (and thus the repository abstraction transitively) will use the mapping metadata of the document class and the mapping subsystem will evaluate the collectionName attribute to find out about the collection to interact with.

Instance answered 1/5, 2013 at 20:34 Comment(9)
Thank you so much for the prompt response Oliver. We've already decided to go with the first approach, the database level. sbzoom had raised a concern with it: "The MongoTemplate runs its ensureIndexes() method from within its constructor. This method calls out the the database to make sure annotated indexes exist in the database. The constructor for MongoTemplate gets called when Spring starts up so I never even have a chance to set a ThreadLocal variable". What's the work around this?Reprisal
@Oliver for multi-tenancy on the collection level, would there be an issue that the Mongo indexes wouldn't be created at startup for the collections? Would it do this on-demand?Maloney
But how shpuld that work with MongoRepositories, they need a MongoTemplate at creation time. I would love to have some mapping like <Tenant, Repository> for my purposes but how to implement this?Bubble
Spring Boot 1.3.3 + Spring Data MongoDB 1.8.2 user here, facing the same index issue. Opened a new question on #37460310Frissell
@Oliver : May be this is a old thread but just want to bring it your kind notice that the multi tenancy on collection does not work.During start up everything is fine but when you access any repository methods it creates tables again with _tablename. May be something has changed in architecture since its 2017 :)Bullfight
The collection solution works fine for me, even on CloudFoundry (SAP Cloud Platform). I'm using Spring Data Mongo and Spring Data Rest. The collection name is taken from the TenantProvider with each individual access of a repository function.Hutch
mongo mutitenancy in general: web.archive.org/web/20140812091703/http://support.mongohq.com/… , so I'd take one DB for all the customers + customer_id in each documentAbsolve
Thanks, works for me on collection level. However, I had to add an '@' before bean name, otherwise it couldn't be found by SpEl: @Document(collectionName = "#{@tenantProvider.getTenantId()}_accounts"Basinger
@Oliver I have a situation where each database is in a separate cluster. Question posted here #63686698 would love to get some help. ThanksHorizontal
B
4

I had a similar approach to Oliver Gierke. At least on database-level. https://github.com/Loki-Afro/multi-tenant-spring-mongodb You should be able to do things like this:

        MultiTenantMongoDbFactory.setDatabaseNameForCurrentThread("test");
        this.personRepository.save(createPerson("Phillip", "Wirth", ChronoUnit.YEARS.between(
                LocalDate.of(1992, Month.FEBRUARY, 3),
                LocalDate.now())));

        System.out.println("data from test: " + this.personRepository.findAll());
//        okay? fine. - lets switch the database
        MultiTenantMongoDbFactory.setDatabaseNameForCurrentThread("test666");

//        should be empty
        System.out.println("data from test666: " + this.personRepository.findAll());
Bubble answered 13/6, 2014 at 12:42 Comment(0)
I
2

for springboot 2.3.3

overriding doGetMongoDatabase helped to achieve multi tenancy

protected MongoDatabase doGetMongoDatabase(String dbName) {   
}

https://github.com/jose-m-thomas/mongo_multi_tenancy_spring_boot_2_3_3

Inflammatory answered 7/9, 2020 at 17:17 Comment(0)
L
2

Full featured multi-tenant/tenancy with Spring Boot + MongoDB + Spring MVC with shared/global database configuration.

https://github.com/arun2pratap/multitenant-spring-mongodb

enter image description here

Laager answered 4/1, 2021 at 15:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.