Prior to Spring Data MongoDB 1.9.0-RELEASE, I was able to create a MongoTemplate object as follows:
new MongoTemplate(client, dbName, credentials). Upon upgrading, this constructor no longer works, giving an error to use MongoCredential instead. However, there is no similar MongoTemplate constructor that uses MongoCredential. It appears that the only way to specify credentials now is when constructing the MongoClient object.
However, since my app is multitenant on the database level, this doesn't work because it does not allow for additional credentials to be added after construction (meaning MongoTemplates cannot be created dynamically). It also is not ideal because if any of the credentials in the list are bad, none of the database connections work, as opposed to just the one with bad credentials.
I also do not want to create a new MongoClient instance for each database. From what I understand, doing so would create a new connection for each database rather than letting MongoClient manage a connection pool, which is ultimately not sustainable since Mongo only allows a finite number of connections.
Do I have any options here besides continuing to use the outdated library?
What you can do is instantiate the MongoClient and cache it using a HashMap with some unique db identifier as key and mongoClient as value. Use that create MongoTemplate
What I ended up doing is creating a single user in the admin database that has access to all of the databases that I need (achieved via the roles array). I create one MongoClient, authorizing as that user against the admin database. Then I am able to create MongoTemplate objects dynamically without issue, because the user I'm authorized as hasreadWrite permissions on those databases.
Related
I have an application that connects to an existing database and retrieves some data from it. This app will use this database in read-only mode. Despite it is our code I would like to add 'fool-level' protection from modifying/deleting documents accidentally by other developers/myself in the future. Tried with pre hooks but it looks that there're different remove hooks, query, model, document, etc... But I couldn't achieve consistency in behavior for all types of removing queries, query, model, document, etc...
Is there any appropriate solution to this task?
Create a read-only user and connect through that user:
https://sysadmins.co.za/create-read-only-users-in-mongodb/
Situation:
For our SaaS API we use schema-based multitenancy, which means every customer (~tenant) has its own separate schema within the same (postgres) database, without interfering with other customers. Each schema consists of the same underlying entity-model.
Everytime a new customer is registered to the system, a new isolated schema is automatically created within the db. This means, the schema is created at runtime and not known in advance. The customer's schema is named according to the customer's domain.
For every request that arrives at our API, we extract the user's tenancy-affiliation from the JWT and determine which db-schema to use to perform the requested db-operations for this tenant.
Problem
After having established a connection to a (postgres) database via TypeORM (e.g. using createConnection), our only chance to set the schema for a db-operation is to resort to the createQueryBuilder:
const orders = await this.entityManager
.createQueryBuilder()
.select()
.from(`${tenantId}.orders`, 'order') // <--- setting schema-prefix here
.where("order.priority = 4")
.getMany();
This means, we are forced to use the QueryBuilder as it does not seem to be possible to set the schema when working with the EntityManager API (or the Repository API).
However, we want/need to use these APIs, because they are much simpler to write, require less code and are also less error-prone, since they do not rely on writing queries "manually" employing a string-based syntax.
Question
In case of TypeORM, is it possible to somehow set the db-schema when working with the EntityManager or repositories?
Something like this?
// set schema when instantiating manager
const manager = connection.createEntityManager({ schema: tenantDomain });
// should find all matching "order" entities within schema
const orders = manager.find(Order, { priority: 4 })
// should find a matching "item" entity within schema using same manager
const item = manager.findOne(Item, { id: 321 })
Notes:
The db-schema needs to be set in a request-scoped way to avoid setting the schema for other requests, which may belong to other customers. Setting the schema for the whole connection is not an option.
We are aware that one could create a whole new connection and set the schema for this connection, but we want to reuse the existing connection. So simply creating a new connection to set the schema is not an option.
To answer my own question:
At the moment there is no way to instantiate TypeORM repositories with different schemas at runtime without creating new connections.
So the only two options that a developer is left with for schema-based multi tenancy are:
Setting up new connections to connect with different schemas within the same db at runtime. E.g. see NestJS Request Scoped Multitenancy for Multiple Databases. However, one should definitely strive for reusing connections and and be aware of connection limits.
Abandoning the idea of working with the RepositoryApi and reverting to using createQueryBuilder (or executing SQL queries via query()).
For further research, here are some TypeORM GitHub issues that track the idea of changing the schema for a existing connections or repositories at runtime (similar to what is requested in the OP):
Multi-tenant architecture using schema. #4786 proposes something like this.photoRepository.useSchema('customer1').find()
Handling of database schemas #3067 proposes something like getConnection().changeDefaultSchema('myschema')
Run-time change of schema #4473
Add an ability to set postgresql schema per call #2439
P.S. If TypeORM decides to support the idea discussed in the OP, I will try to update this answer.
Here is a global overview of the issues with schema-based multitenancy along with a complete walkthrough a Github repo for it.
Most of the time, you may want to use Postgres Row Security Policy instead. It gives most of the benefits of schema-based multitenancy (especially on developer experience), without the issues related to the multiplication of connections.
Since commenting does not work for me, here a hint from the documentation of NestJS:
https://docs.nestjs.com/techniques/database#async-configuration
I am not using NestJS but reading the docs at the moment to decide, if it's a fitting framework for us. We have an app where only some modules have multi tenancy with schema per tenant, so using TypeOrmModule.forRootAsync(dynamicCreatedDbConfig) might be an option for me too.
This may help you if you have an interceptor or middleware, which prepares the dynamicCreatedDbConfig data before...
I have a repository which I want to save inside two different MongoDB databases, programmatically.
If the user enters an URL with the parameter DB1, the repository will save inside the database DB1, if it is DB2, to the database DB2, etc.
Is there any way to do this?
Not automatically. You need to have the application connected to 2 DBs and call each one depending on the parameter that comes in from the request. You'll need two separate repositories as far as I'm aware.
It is not possible to do with repositories easily (use multiple repositories with a little modification, because we can't use one, for each parameter in the URL is a madness.
So to avoid a lot of duplicate code we have to use the Java Driver.
MongoClient mongoClient = new MongoClient("localhost", 27017);
//here we can change the database name
MongoOperations mongoOperations = new MongoTemplate(mongoClient, database);
mongoOperations.save(YOUR_POJO);
mongoClient.close();
The POJO object has to use de #Documentannotation, if not you will have codec problems. Here you can solve them: http://mongodb.github.io/mongo-java-driver/3.2/bson/codecs/
Multi-tenancy on the database level: One way to separate data for multiple clients is to have individual databases per tenant.
Suppose you are working on webservice or web application.
A client will send tenant id as header parameter.
Write one filter and get the tenant details from the Cache by tenant id.
Set the schema name in the filter
for example:
MDC.put(Constants.MONGO_TENANT_DB, "uat");
MongoMultiTenancyInterceptor will get called before any DB operation/orm call.
String tenantDBName = MDC.get(Constants.MONGO_TENANT_DB);
LOG.info("Switching to database: "+tenantDBName);
if(StringUtils.isNotBlank(tenantDBName)){
MultitenantDatastoreFactory.setDatabaseNameForCurrentThread(tenantDBName);
}
Setting the Database name for current Thread.
MultitenantDatastoreFactory : Create datastore instance per tenant schema and store into HashMap.
MultitenantDatastoreFactory.getDS(): Returns the datastore from the Hashmap by schema name
UserRepository: It will get Datastore object from the factory method and perform any DB operation.
I have uploaded my project in GITHUB, you can download the code from GITHUB
https://github.com/vikashnitk50/spring-morphia-db-poc
spring-data-mongodb. How can i dynamically create a database in mongo using spring-data-mongodb library?
I am trying to use Spring-Mongodb-Data module for CRUD operations against Mongo database and going through examples and articles my assumption is that databasename should be pre-defined in spring context xml when defining MongoTemplate bean.
In my case I have an multi-tenant application that will accept requests over http and my application should create the mongodatabase on-the-fly and use the name provided in the input http request to create the database and then load the data into collection in the newly created database.
I am trying to figure out if there is a way to dynamically populate the databasename in MongoTemplate or MongoRepository without having to provide it in spring context.xml?
Please help me.
Thanks
-RK
Have you tried the following instead of going through the pre-defined spring context configuration.
MongoTemplate getMongoTemplate(Mongo mongo, String database) {
return new MongoTemplate(mongo, database);
}