几个月前,我不得不使用 Node.js 构建一个基于 URL 的多租户 SaaS 产品。我不知道它是什么。所以我在 Google 上搜索了“什么是多租户架构”、“如何在 Node.js 中构建多租户架构”等等。不幸的是,我找不到很多有用的内容,但最后,我发现了 Knex(发音为 /kəˈnɛks/),它是一个强大的 SQL 查询构建器。我想借此机会分享一些我希望一些人会发现相关和有用的东西。

多租户架构是一种软件架构,其中软件的单个实例可以服务于多个用户组。这些用户组称为租户。

[单多租户](https://res.cloudinary.com/practicaldev/image/fetch/s--6RX9Vs1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev -to-uploads.s3.amazonaws.com/uploads/articles/e8jezfnv91d2gwb2x8i9.jpg)

在单租户中,

  • 单独的应用程序。

  • 独立数据库

在多租户中,

  • 相同申请

  • 独立数据库

多租户类型

在分离租户数据时,有两种主要的多租户架构模型

  1. 每个租户的单一数据库

  2. 单个数据库,每个租户都有不同的表

[Types-Of-Multitenancy](https://res.cloudinary.com/practicaldev/image/fetch/s--VCaNR2ou--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev -to-uploads.s3.amazonaws.com/uploads/articles/2f5cryemk9llfn6xtdfl.jpg)

Knex.js

根据其官方网站,Knex.js 是一个“包含电池”的 SQL 查询构建器,适用于 PostgreSQL、CockroachDB、MSSQL、MySQL、MariaDB、SQLite3、Better-SQLite3、Oracle 和 Amazon Redshift,旨在灵活、便携、使用起来很有趣。

现在让我们动手在 Node.js 中构建多租户架构。在以下示例中,我们将为每个租户的方法在单个数据库中使用不同的表。

先决条件

  • 带有 express 库的 Node.js 基础知识

设置

创建一个新文件夹并通过在 CLI 中输入以下命令创建package.json文件来初始化 node.js 项目。

$ npm init -y

进入全屏模式 退出全屏模式

安装expressknexpg软件包。 (pg 是 Node.js 的 PostgreSQL 客户端)

$ npm install express knex pg

进入全屏模式 退出全屏模式

在根文件夹中创建index.js文件。这将是应用程序的入口点。

数据库架构

[图像描述](https://res.cloudinary.com/practicaldev/image/fetch/s--xK5ca3Y9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/huhaoayq35hezbwvf4hk.png)

代码说明

注意:为了便于理解,我没有使用最佳代码实践

1\。 Knex 配置

创建一个db.js文件。这是我们将配置 Knex 以建立与数据库的连接的地方。

db.js

使用require()函数导入knex模块。

const knex = require("knex");

进入全屏模式 退出全屏模式

调用由 Knex 模块导出的顶级函数knex(),该函数接受一个配置对象,并接受一些参数。

const db = knex({
  client: "postgresql",
  connection: {
    database: "mutitenancy-node",
    user: "postgres",
    password: "postgres",
  },
});

进入全屏模式 退出全屏模式

导出db变量,以便它可以在应用程序的其他地方使用。

module.exports = db;

进入全屏模式 退出全屏模式

2\。设置基本服务器

index.js

使用require()函数在您的应用程序中导入 express。

const express = require("express");

进入全屏模式 退出全屏模式

调用 express 模块导出的顶层函数express()

const app = express()

进入全屏模式 退出全屏模式

db.js文件中导入默认导出功能

const knex = require("./db.js");

进入全屏模式 退出全屏模式

使用app对象的use()方法挂载express.json()中间件函数,解析请求体中的 JSON。

app.use(express.json());

进入全屏模式 退出全屏模式

通过app对象的listen()方法监听服务器。

app.listen(4000, () => {
  console.log("Server listening to Port 4000");
});

进入全屏模式 退出全屏模式

3\。创建租户

通过迁移或手动使用以下字段创建tenants表。

  • id - uuid

  • 名称 - 字符不同

  • 子域 - 字符不同

  • admin_email - 字符不同

每当新租户注册到我们的 SaaS 应用程序时,将他们的详细信息插入tenants表,并为租户的用户创建一个users表,前缀为子域名 (tenantname_users)。

使用创建 POST 请求路由/create-tenant

app对象的post()方法。

app.post('/create-tenant', async (req, res) => {


})

进入全屏模式 退出全屏模式

在回调函数体中,从请求体中获取namesubdomainadminEmail属性的值。

const { name, subdomain, adminEmail } = req.body;

进入全屏模式 退出全屏模式

将租户的详细信息插入到tenants表中

await knex("tenants").insert({
      name,
      subdomain,
      admin_email: adminEmail,
    });

进入全屏模式 退出全屏模式

现在,为租户的用户创建一个表

await knex.schema.createTable(`${subdomain}_users`, (table) => {
    table.uuid("id").defaultTo(knex.raw("uuid_generate_v4()"));
    table.string("first_name");
    table.string("last_name");
    table.string("email").unique();
  });

进入全屏模式 退出全屏模式

使用send()方法将响应发送回客户端。

  res.send("Tenant Created");

进入全屏模式 退出全屏模式

4\。插入users

使用创建 POST 请求路由/create-user

app对象的post()方法。

app.post('/create-user', async (req, res) => {


})

进入全屏模式 退出全屏模式

使用req对象的subdomains数组获取请求客户端的子域。

const subdomain = req.subdomains[0];

进入全屏模式 退出全屏模式

注意:要为每个租户提供动态 URL,您可能需要在前端应用程序中使用 Nginx。

由于我们使用的是 localhost,因此请从请求正文中获取子域和用户详细信息。

const { firstName, lastName, email, subdomain } = req.body;

进入全屏模式 退出全屏模式

将用户的详细信息插入到该特定租户的users表中

await knex(`${subdomain}_users`).insert({
    first_name: firstName,
    last_name: lastName,
    email,
  });

进入全屏模式 退出全屏模式

使用send()方法将响应发送回客户端。

res.send("User Created !!");

进入全屏模式 退出全屏模式

总结

由于我们使用的是单个数据库,来自多个租户的请求可能会导致 noisy-neighbor effect,从而导致网络性能问题。

完整代码

Github

学分

  • 由 Anggara 创建的 Seo 和 web 图标 - Flaticon

  • Freepik 创建的用户图标 - Flaticon

  • Smashicons 创建的数据库图标 - Flaticon

Logo

PostgreSQL社区为您提供最前沿的新闻资讯和知识内容

更多推荐