发布于 2026-01-06 1 阅读
0

Node.js 和 PostgreSQL 多租户基础知识

Node.js 和 PostgreSQL 多租户基础知识

二月会前进吗?不会,但四月可能会。😂

我知道,这是一个很糟糕的玩笑,但我也知道,如果你继续阅读这篇文章,你将学会如何创建自己的基本多租户Node.js和PostgreSQL API的基础知识。

多租户架构是如何运作的?

简单来说,就是在一个共享的基础设施上运行着一套代码库,但每个客户都拥有一个独立的数据库。
以 Jira 为例,它是最流行的在线项目任务管理工具,用于跟踪错误和问题,以及进行运维项目管理。每个组织都有自己的仪表盘,通过自定义子域名访问。A 和 B 可以访问相同的功能,接收相同的更新,但 A 的问题、工单、评论、用户等信息 B 无法访问,反之亦然。Slack
是另一个多租户的例子,其工作方式与 Jira 类似……当然,这里我们主要讨论的是用户、频道、项目经理、通知等等。

何时必须使用多租户?

试想一下,你一直在开发一款很棒的应用程序,它可以作为 SaaS 提供。提供 SaaS 应用程序的方式有很多种,但如果你的软件需要保持数据库隔离,同时又能为每个客户提供相同的功能,那么它就是必需的。

为什么?

多租户应用程序的优势之一在于代码库的可维护性,因为所有客户端的代码始终相同。如果某个客户端报告问题,解决方案将应用于其他 999 个客户端。需要注意的是,如果您输入错误,该错误也会影响所有客户端。至于数据库管理,可能会稍微复杂一些,但只要遵循适当的模式和约定,一切都会顺利进行。数据库管理有多种方法(例如分布式服务器隔离、使用独立数据集的数据库、使用独立模式的数据库、行隔离),每种方法当然都有其优缺点。

你想学习编程吗?

我选择使用独立的数据库作为数据库方法,因为我认为这对这个例子来说更容易,而且,由于 Sequelize 需要大量的配置,所以我改用了 Knex。

我将重点介绍实现多租户 Node.js 和 PostgreSQL 工作流程所需的特定文件。

多租户 Node.js 和 PostgreSQL

创建用于管理租户的通用数据库

CREATE DATABASE tenants_app; 

CREATE TABLE tenants (  
  id SERIAL PRIMARY KEY,   
  uuid VARCHAR(255) UNIQUE NOT NULL,  
  db_name VARCHAR(100) UNIQUE NOT NULL,  
  db_username VARCHAR(100),  
  db_password TEXT,   
  created_at TIMESTAMP DEFAULT NOW(),  
  updated_at TIMESTAMP DEFAULT NOW()
); 
Enter fullscreen mode Exit fullscreen mode

database.js:建立与​​主数据库的连接

const knex = require('knex') 
const config = {   
  client: process.env.DB_CLIENT,  
  connection: {   
    user: process.env.DB_USER,     
    host: process.env.DB_HOST,     
    port: process.env.DB_PORT,     
    database: process.env.DB_DATABASE,    
    password: process.env.DB_PASSWORD   
   } 
 } 
 const db = kenx(config) 
 module.exports = { db, config } 
Enter fullscreen mode Exit fullscreen mode

connection-service.js:用于准备租户数据库连接,换句话说,就是用于在正确的数据库中运行查询的连接。

const knex = require('knex')
const { getNamespace } = require('continuation-local-storage') 
const { db, config } = require('../config/database') let tenantMapping 

const getConfig = (tenant) => {   
  const { db_username: user, db_name: database, db_password: password } = tenant   
  return {     
    ...config,    
    connection: {       
      ...config.connection,       
      user,       
      database,     
      password    
    }  
  }
} 

const getConnection = () => getNamespace('tenants').get('connection') || null 

const bootstrap = async () => { 
  try {     
    const tenants = await db       
      .select('uuid', 'db_name', 'db_username', 'db_password')     
      .from('tenants')    

    tenantMapping = tenants.map((tenant) => ({                       
      uuid: tenant.uuid,       
      connection: knex(getConfig(tenant))   
    }))  
 } catch (e) {     
   console.error(e)   
 } 
} 

const getTenantConnection = (uuid) => {   
  const tenant = tenantMapping.find((tenant) => tenant.uuid === uuid)  

  if (!tenant) return null   

  return tenant.connection
} 
Enter fullscreen mode Exit fullscreen mode

tenant-service.js:用于为每个新客户端创建数据库,使用相同的数据库结构,并在需要时用于删除数据库。

const Queue = require('bull')
const { db } = require('../config/database') 
const migrate = require('../migrations') 
const seed = require('../seeders') 
const { bootstrap, getTennantConnection } = require('./connection') 

const up = async (params) => {  
  const job = new Queue(    
    `setting-up-database-${new Date().getTime()}`,             
    `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`   
)   
job.add({ ...params })   
job.process(async (job, done) => {   
  try {      
    await db.raw(`CREATE ROLE ${params.tenantName} WITH LOGIN;`) // Postgres requires a role or user for each tenant       
    await db.raw(         
      `GRANT ${params.tenantName} TO ${process.env.POSTGRES_ROLE};`       
) // you need provide permissions to your admin role in order to allow the database administration       
    await db.raw(`CREATE DATABASE ${params.tenantName};`)       
    await db.raw(         
      `GRANT ALL PRIVILEGES ON DATABASE ${params.tenantName} TO ${params.tenantName};`
)      
    await bootstrap() // refresh tenant connections to include the new one as available  
    const tenant = getTenantConnection(params.uuid)       
    await migrate(tenant) // create all tables in the current tenant database      
    await seed(tenant) // fill tables with dummy data     
  } catch (e) {      
    console.error(e)    
   }   
 }) 
} 
Enter fullscreen mode Exit fullscreen mode

tenant.js:用于处理列出、创建或删除租户请求的控制器

const { db } = require('../config/database') 
const { v4: uuidv4 } = require('uuid') 
const generator = require('generate-password') 
const slugify = require('slugify') 
const { down, up } = require('../services/tenant-service') 

// index 

const store = async (req, res) => {   
  const {    
    body: { organization }   
  } = req   

  const tenantName = slugify(organization.toLowerCase(), '_')   
  const password = generator.generate({ length: 12, numbers: true })  
  const uuid = uuidv4()   
  const tenant = {     
    uuid,    
    db_name: tenantName,     
    db_username: tenantName,     
    db_password: password   
  }   
  await db('tenants').insert(tenant)   
  await up({ tenantName, password, uuid })   

  return res.formatter.ok({ tenant: { ...tenant } }) 
} 

const destroy = async (req, res) => {   
  const {     
    params: { uuid }   
  } = req   

  const tenant = await db    
    .select('db_name', 'db_username', 'uuid')     
    .where('uuid', uuid)    
    .from('tenants')   

   await down({     
     userName: tenant[0].db_username,    
     tenantName: tenant[0].db_name,    
     uuid: tenant[0].uuid  
   })  
   await db('tenants').where('uuid', uuid).del() 

   return res.formatter.ok({ message: 'tenant was deleted successfully' }) } 

module.exports = {  
  // index, 
  store,   
  destroy 
} 
Enter fullscreen mode Exit fullscreen mode

如下面的图片所示,API 现在能够创建多个客户端,共享服务、端点和其他内容,但保持数据库隔离。

步骤1

步骤2

步骤3

步骤4

太酷了!

是的,多租户 Node.js 和 PostgreSQL 并没有听起来那么复杂。当然,需要考虑很多因素,例如基础设施、CI/CD、最佳实践和软件模式等等,但只要逐一处理,一切都会顺利进行。正如你所看到的,这种架构可以帮助你的业务实现你想要的扩展,因为云本身就是极限,而目前云的扩展能力是无限的。当然,如果你想查看完整的代码,可以点击这里

更新:

我创建了一个分支来应用这个概念,使用 MySQL 作为数据库,此外,我也会尽快尝试添加对 Mongoose 的支持。

文章来源:https://dev.to/agusrdz/basics-of-multi-tenant-node-js-and-postgresql-30fl