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 fro
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
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());
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.
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:
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.
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.