我们在SaaS应用程序中使用回收站4。我们被困在一个案子里。
我们试图让每个用户都有自己独立的数据库。因此,当用户登录应用程序时,我们希望创建一个动态数据源,这就是我们所做的。但问题是如何将存储库链接到动态创建的数据源。
我们尝试过的一种解决方案是通过this.datasource对每个用户请求更改存储库中的controller,但是当多个用户同时请求时,datasource值就会发生变化。这是个卑微的要求,请帮帮我们。
我知道我可能解释得不对。
发布于 2020-06-19 14:54:58
我们正在讨论如何在GitHub问题回环-下一个#5056中实现租户隔离的不同方法。我们还提供了一个多租户应用程序示例,参见它的自述文件。拉请求回环-下一个#5681正在实现一个通用池服务,该服务也可用于实现租户隔离。
恐怕您的问题没有提供足够的细节来允许我给您一个具体的解决方案,所以我将引用我的基于数据源的租户隔离提案中的代码片段。
为了方便地注入特定于租户的数据源,让我们保留相同的数据源名称(绑定键),例如datasources.tenantData,但是实现数据源值的动态解析。其思想是将由lb4 datasource构建的数据源类重新工作到提供者类中。
import {inject} from '@loopback/core';
import {juggler} from '@loopback/repository';
const config = {
name: 'tenantData',
connector: 'postgresql',
// ...
};
export class TenantDataSourceProvider implements Provider<TenantDataSource > {
constructor(
@inject('datasources.config.tenant', {optional: true})
private dsConfig: object = config,
@inject(SecurityBindings.USER)
private currentUser: UserProfile,
) {}
value() {
const config = {
...this.dsConfig,
// apply tenant-specific settings
schema: this.currentUser.name
};
// Because we are using the same binding key for multiple datasource instances,
// we need to implement our own caching behavior to support SINGLETON scope
// I am leaving this aspect as something to figure out as part of the research
const cached = // look up existing DS instance
if (cached) return cached;
const ds = new TenantDataSource(config);
// store the instance in the cache
return ds;
}
}
export class TenantDataSource extends juggler.DataSource {
static dataSourceName = 'tenant';
// constructor is not needed, we can use the inherited one.
// start/stop methods are needed, I am skipping them for brevity
}如何实现每个租户数据源的缓存有不同的方法。理想情况下,我希望重用上下文。结果发现这很简单!
我们希望每个租户数据源都有自己的数据源名称和绑定密钥。为了允许存储库通过@inject获得数据源,我们可以实现一个“代理”数据源提供程序,它将使用名称数据源之一进行解析。
export class TenantDataSourceProvider implements Provider<TenantDataSource> {
private dataSourceName: string;
private bindingKey: string;
constructor(
@inject('datasources.config.tenant', {optional: true})
private dsConfig: object = config,
@inject(SecurityBindings.USER)
private currentUser: UserProfile,
@inject.context()
private currentContext: Context,
@inject(CoreBindings.APPLICATION_INSTANCE)
private app: Application,
) {
this.dataSourceName = `tenant-${this.currentUser.name}`;
this.bindingKey = `datasources.${this.dataSourceName}`;
}
value() {
if (!this.currentContext.isBound(this.bindingKey)) {
this.setupDataSource();
}
return this.currentContext.get<juggler.DataSource>(this.bindingKey);
}
private setupDataSource() {
const resolvedConfig = {
...this.dsConfig,
// apply tenant-specific settings
schema: this.currentUser.name,
};
const ds = new TenantDataSource(resolvedConfig);
// Important! We need to bind the datasource to the root (application-level)
// context to reuse the same datasource instance for all requests.
this.app.bind(this.bindingKey).to(ds).tag({
name: this.dataSourceName,
type: 'datasource',
namespace: 'datasources',
});
}
}
export class TenantDataSource extends juggler.DataSource {
// no static members like `dataSourceName`
// constructor is not needed, we can use the inherited one.
// start/stop methods are needed, I am skipping them for brevity
}上面的代码示例在每个租户提出第一个请求时自动创建每个租户的数据源。这将提供更快的应用程序启动,并可能减少对数据库的压力,在大多数租户连接的情况下,只是很少使用应用程序。另一方面,只有在发出第一个请求之后才会发现特定于租户的数据库连接的任何问题,这可能为时已晚。如果您希望在启动时立即建立(并检查)所有租户数据库连接,则可以将代码从setupDataSource移动到引导脚本,并为每个已知的租户调用它。
另见以下评论
考虑到如何将这些数据源注入存储库,因为
this.bindingKeys是动态生成的?
其想法是将静态数据源键绑定到TenantDataSourceProvider,这将解析为动态创建的数据源之一。
例如,在应用程序构造函数中:
this.bind('datasources.tenant').toProvider(TenantDataSourceProvider);然后您可以按照通常的方式注入数据源,例如:
@inject('datasources.tenant')
dataSource: TenantDataSourcehttps://stackoverflow.com/questions/62328200
复制相似问题