将单个存储库链接到多个数据源 -- 环回 4.



我们在SaaS应用程序中使用Loopback 4。我们被困在一个案例中。

我们试图让每个用户都有自己独立的数据库。因此,当用户登录应用程序时,我们希望创建一个动态数据源,这就是我们所做的。但问题是如何将存储库链接到动态创建的数据源。

我们尝试的一种解决方案是通过controller更改每个用户请求时存储库中的this.datasource,但是当多个用户同时请求时,datasource值会发生变化。 这是一个谦虚的请求,请帮助我们。

我知道我可能没有正确解释。

> 我们正在讨论如何在 GitHub 问题环回-next#5056 中实现租户隔离的不同方法。我们还提供了一个示例多租户应用程序,请参阅其自述文件。拉取请求环回-next#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: TenantDataSource

最新更新