TypeORM:如何在运行时动态设置EntityManager(或repositories)的数据库模式?

24

情况:

对于我们的SaaS API,我们使用基于模式的多租户,这意味着每个客户(~租户)在同一个(postgres)数据库中拥有自己独立的模式,不会干扰其他客户。每个模式都由相同的底层实体模型组成。

每当系统注册新客户时,就会在数据库中自动创建一个新的隔离模式。这意味着,该模式是在运行时创建的,而事先不知道。客户的模式名称根据客户的域名命名。

对于到达我们的API的每个请求,我们从JWT中提取用户的租户关联,并确定要使用哪个db模式来执行此租户的请求的db操作。

问题

在通过TypeORM(例如使用createConnection)建立与(postgres)数据库的连接后,我们唯一可以设置db操作的模式的机会是诉诸createQueryBuilder

const orders = await this.entityManager
  .createQueryBuilder()
  .select()
  .from(`${tenantId}.orders`, 'order') // <--- setting schema-prefix here
  .where("order.priority = 4")
  .getMany();

这意味着我们被迫使用QueryBuilder,因为在使用EntityManager API(或Repository API)时似乎无法设置模式。但是,我们想/需要使用这些API,因为它们更简单易写,需要的代码更少,而且也不容易出错,因为它们不依赖于手动编写基于字符串的查询语句。
问题:
在TypeORM中,是否可以在使用EntityManager或repositories时设置数据库模式?
例如这样:
// 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 })

注意:

  • 需要以请求范围的方式设置db-schema,以避免为其他客户的请求设置模式。设置整个连接的模式不是一个选项。
  • 我们知道可以创建一个全新的连接并为该连接设置模式,但我们希望重用现有的连接。因此,仅创建新连接以设置模式不是一个选项。

1
我在我的项目中有完全相同的设置,每个租户都有自己的模式,每个模式看起来都一样,连接需要请求范围。我正在走你不想走的路,也就是为每个客户创建一个新的连接。你不这样做的意图是什么?性能可能是一个原因,但目前我没有遇到太多数据库连接的问题。数据安全不应该是一个问题,按照你的方式做并没有区别:const manager = ConnectionUtils.createConnection(schema).createEntityManager(); - JudgeFudge
1
嗨@JudgeFudge,非常感谢您的反馈!我可以问一下,您的后端每分钟收到多少请求,通常同时保持多少并发连接?此外,在哪个时刻关闭db-connection?您是将它们保持空闲还是在通过中间件向客户端发送响应后立即关闭它们?我不想打开新连接的原因是为了避免不必要的资源使用(内存/CPU),而且,如果理论上我们可以使用TypeORM设置不同的模式,那么重新建立连接就没有意义...真的很烦人:( - Felix K.
1
我查看了TypeORM的源代码,试图找到一种动态设置模式的方法,但似乎没有简单的方法(我猜你找到的方法是最不脏的方法)。 我有大约50个租户,请求的数量并不高(每个租户每小时大约50个请求),所以我无法告诉您有关扩展的更多信息。因此,我认为我们有两个选择:创建一个更改请求(也许自己做)或者在拥有多个连接时了解更多性能方面的信息。由于我对这个话题也非常感兴趣,我可能会在我的应用程序中尝试一些测试场景。我会随时向您更新。 - JudgeFudge
嗨@B12Toaster,你有找到答案吗? 我也有完全相同的问题,但TypeORM团队没有回应... 只是想知道你是否找到了任何好的解决方案。 - WinterTime
@B12Toaster,在创建模式后,您如何运行初始迁移。我想实现您的方法,但卡在那里了。 - namus
显示剩余3条评论
3个回答

23
回答我的问题:
目前没有办法在运行时使用不同的架构实例化TypeORM repositories,而不创建新的连接。
因此,基于架构的多租户留给开发人员的唯一两个选项是:
1.在运行时设置新的连接以连接到同一数据库中的不同模式。例如,请参见NestJS Request Scoped Multitenancy for Multiple Databases。但是,应该努力重用连接并注意connection limits
2.放弃使用RepositoryApi的想法,并返回使用createQueryBuilder(或通过query()执行SQL查询)。
为了进一步研究,这里有一些TypeORM GitHub问题,跟踪更改运行时现有连接或存储库的架构的想法(类似于OP中请求的内容):
- Multi-tenant architecture using schema. #4786 提出了类似于this.photoRepository.useSchema('customer1').find()的内容。 - Handling of database schemas #3067 提出了类似于getConnection().changeDefaultSchema('myschema')的内容。 - Run-time change of schema #4473 - Add an ability to set postgresql schema per call #2439 附言:如果TypeORM决定支持OP中讨论的想法,我将尝试更新此答案。

想知道Typeorm是否支持单个数据库的多租户,有没有更新? - Anesh P.
我想知道为什么 TypeORM 没有一个针对如此常见的事情的清理方法。 - juztcode

2
这里是一个关于基于模式的多租户问题的全局概述,其中包括了一个完整的Github仓库演示。
大多数情况下,您可能想使用Postgres行安全策略。它提供了基于模式的多租户的大部分好处(特别是在开发者体验方面),而不会出现与连接乘法相关的问题。

@Felix K.,这个答案中提供的链接(第一个)不是合适的解决方案吗?如果池中的连接存在,则不应该重新创建,对吧? - juztcode
这个答案提供的链接(第一个)不是适当的解决方案吗?如果池中已经存在连接,那么不应该重新创建连接吧? - juztcode

0

由于评论对我不起作用,这里提供一条来自NestJS文档的提示:

https://docs.nestjs.com/techniques/database#async-configuration

我目前没有使用NestJS,但正在阅读文档以决定是否适合我们。我们有一个应用程序,只有一些模块具有每个租户的模式多租户功能,因此对我来说,使用TypeOrmModule.forRootAsync(dynamicCreatedDbConfig)也可能是一个选项。

如果您有拦截器或中间件,可以在之前准备dynamicCreatedDbConfig数据,这可能会对您有所帮助...


抱歉,但这并没有解答我的问题。 - Felix K.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接