0

目标

我正在尝试使用 Feathers JS 构建一个多租户应用程序。登录时,租户 ID 将包含在请求中。从那时起,租户 ID 将从user带有请求参数的字段中获取。每个用户都有这个tenantId字段。

在 MongoDB 中,我对每个租户的每种数据类型都有一个唯一的集合。集合名称看起来tenantId.documentstenantId.users

问题

通过feathers generate serviceCLI 命令生成的服务如下所示:

export class Documents extends Service {
  //eslint-disable-next-line @typescript-eslint/no-unused-vars
  constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
    super(options);

    const client: Promise<Db> = app.get('mongoClient');

    client.then(db => {
      this.Model = db.collection('documents');
    });
  }
}

如您所见,生成Service的 s 在实例化期间似乎需要它们的集合名称(在本例中为“文档”)。通常,这是有道理的,因为它可以节省await调用app.get("mongoClient")

但是,由于我需要根据 User 动态更改从哪个集合中读取tenantId,因此这对我不起作用。

我实现了以下内容:

export class Documents extends Service {
  client: Promise<Db>
  //eslint-disable-next-line @typescript-eslint/no-unused-vars
  constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
    super(options);

    this.client = app.get("mongoClient");
  }

  async create(data: IDocumentData, params: Params) {
    const db: Db = await this.client;
    this.Model = db.collection(`${params.user!!.organizationId}.documents`);
    return super.create(data, params);
  }
}

问题是这些:

  1. 我需要await this.client每个请求,即使在用户实际向该服务发出请求时承诺可能已经实现
  2. Service即使我几乎不需要添加任何真正的功能,我也必须实现父级的每个方法。

问题

解决这个问题的最简单的方法是什么?

  • 我不想覆盖每个服务中我需要的每个方法
  • 我看不到用中间件或钩子来处理这个问题的方法。
  • 我也不认为有必要在我的应用程序中为每个租户创建一个服务实例。看起来很浪费,因为我不需要根据租户 ID 发出任何额外的外部请求,我只需要更改集合

在 Feathers 中是否有一个好的、漂亮的方法来做到这一点?

4

1 回答 1

1

感谢有帮助的 Feather 社区 Slack 频道,我想我找到了一个解决这个特定问题的中规中矩的解决方案。它并没有解决我所有的顾虑,但至少它使我的代码变得杂乱无章。

Service首先,我应该创建一个新类来扩展实现我想要的功能的内置类。它可能看起来像这样:

class DynamicMongoService extends Service {
  client: Promise<Db>;
  collectionName: string;

  constructor(
    options: Partial<MongoDBServiceOptions>,
    app: Application,
    collectionName: string
  ) {
    super(options);

    this.client = app.get("mongoClient");
    this.collectionName = collectionName;
  }

  async getCollection(params: Params) {
    const db: Db = await this.client;
    this.Model = db.collection(
      `${params!!.user!!.organizationId}.${this.collectionName}`
    );
  }

  async find(params?: Params) {
    await this.getCollection(params!!);
    return super.create(params!!);
  }

  async get(id: Id, params?: Params) {
    await this.getCollection(params!!);
    return super.get(id, params);
  }

  async create(data: Partial<any> | Array<Partial<any>>, params?: Params) {
    await this.getCollection(params!!);
    return super.create(data, params);
  }

  async update(id: NullableId, data: any, params?: Params) {
    await this.getCollection(params!!);
    return super.update(id!!, data, params);
  }

  async patch(id: NullableId, data: Partial<any>, params?: Params) {
    await this.getCollection(params!!);
    return super.patch(id!!, data, params);
  }

  async remove(id: NullableId, params?: Params) {
    await this.getCollection(params!!);
    return super.patch(id!!, params!!);
  }
}

因此,关键要素是:

  1. 在构造函数中传递集合名称
  2. 在每个方法之前获取集合名称

此服务的实现如下所示:

export class Documents extends DynamicMongoService {
  constructor(options: Partial<MongoDBServiceOptions>, app: Application) {
    super(options, app, "documents");
  }
}

不是最好的,但很容易!

于 2021-02-10T00:53:50.737 回答