Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Looking for advice on schema-based (Postgres) multi-tenancy using Exposed and Ktor #1493

Open
nhalase opened this issue Apr 19, 2022 · 2 comments

Comments

@nhalase
Copy link

nhalase commented Apr 19, 2022

I'm looking for advice on a good way to handle schema-based multi-tenancy with Exposed. I'm getting tripped up because of Ktor and Coroutines and I'm coming from a Spring background where I would normally just use MultiTenantConnectionProvider from Hibernate.

Context:

  • Ktor
  • Postgres
  • Identical Schema per Tenant (managed by Flyway)
  • Schema name is derived from Auth
  • Connection pooling using Hikari

Here is my current solution:

import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction

suspend fun <T> suspendedTransaction(schema: String, block: () -> T): T = newSuspendedTransaction(Dispatchers.IO) {
    SchemaUtils.setSchema(schema = Schema(schema))
    val result = block()
    SchemaUtils.setSchema(schema = publicSchema)
    result
}

Theoretically, this could work, too:

import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction

suspend fun <T> suspendedTransaction(schema: String, block: () -> T): T = newSuspendedTransaction(Dispatchers.IO) {
    connection.schema = schema
    val result = block()
    connection.schema = publicSchema.identifier
    result
}

Questions:

  1. Is there a difference between the above?
  2. Is there a better way of doing what I'm trying to do?
  3. Is what I'm currently doing "safe" in a Ktor server context?

Thanks!

@edeak
Copy link

edeak commented May 10, 2022

This looks a good approach to me. Another option could be having separate Database instance for all the tenant databases, but that would make connection pooling trickier and probably it would waste a lot of resources.

First approach looks more robust because it delegates the database specific switch to the framework, so if you switch from Postgres, you probably don't have to rewrite this part.

@cbergau
Copy link

cbergau commented May 1, 2023

We are having the same thing right now.

Actually, in order to make it work, we have to put the schema name in double quotes, so the resulting sql is

SET search_path TO "CUSTOMER123"

Here the Exposed Code:

transaction {
    SchemaUtils.setSchema(schema = Schema("\"$clientId\""))
    // whatever query here
}

I do not like this solution. Also i am not sure about security here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants