Serverless 最佳实践之数据库的链接和查询

Serverless 最佳实践的第二讲来了,本讲将帮你 Get 如下技巧:git

  • 利用云函数的生命周期来管理数据库链接,下降链接数并提高性能
  • 使用 Knex 简化 Sql 拼接,并与 TypeScript 结合提高代码质量
  • 适时分库提高数据库性能、下降耦合和避免太高的链接数

利用云函数的生命周期来管理数据库链接

在第一讲云函数的生命周期中,咱们已经提到了在云函数 Mount 阶段建立数据库链接带来的两方面好处:github

  • 有效下降数据库链接数(每一个请求建立一个链接 -> 每一个实例建立一个链接)
  • 性能优化(每一个请求建立一个链接 -> 多个请求复用实例的链接)

咱们再回顾一下示例代码:sql

import { Func } from '@faasjs/func'; // FaasJS 的云函数类
import { Sql } from '@faasjs/sql'; // FaasJS 的 Sql 插件

// 初始化数据库对象
const sql = new Sql();

// 返回云函数实例
export default new Func({
  plugins: [sql], // 插件管理,FaasJS 将自动管理插件的生命周期
  async handler(){ // 业务代码
    return await sql.query('SELECT * FROM users WHERE id = ?', [1]);
  }
});
复制代码

FaasJS 的 Sql 插件支持 Mysql、PostgreSql 和 Sqlite 及支持这三类数据库协议的数据库,且已经内部封装了基于云函数生命周期机制的最佳实践,开发者只需直接使用便可。typescript

使用 Knex、TypeScript 结合提高开发效率和质量

Knex 是一个 SQL 语句生成插件,而且能够与 TypeScript 结合,大幅简化开发者对数据库的操做。数据库

咱们直接看代码示例:性能优化

// user.func.ts
import { Func } from '@faasjs/func'; // FaasJS 的云函数类
import { Sql } from '@faasjs/sql'; // FaasJS 的 Sql 插件
import knex from 'knex';

// 使用 TypeScript 来定义用户表的结构
interface User {
  id: number;
  name: string;
}

// 初始化数据库对象
const sql = new Sql();

// 返回云函数实例
export default new Func({
  plugins: [sql], // 插件管理,FaasJS 将自动管理插件的生命周期
  async handler(){ // 业务代码
    const users = knex<User>({
        client: sql.adapterType
      }) // 告诉 Knex 返回的数据类型和数据库的类型
      .from('users') // 告诉 Knex 表名
      .connection(sql.adapter!.pool); // 复用 sql 插件自动维护的数据库链接
    
    return await users.where({ id: 1 }); // Knex 形式的数据库查询
  }
});
复制代码

上面的代码中有两个要点:微信

  • Knex 支持使用 TypeScript 的 interface 做为返回数据类型
  • sql 插件须要把链接池注入到 Knex 中以利用云函数的生命周期来管理链接

按上面的写法,云函数自己的业务代码是没问题了,但 Knex 还支持建表之类的操做,对于自动化测试是很是有用的,因此咱们再深刻看一下自动化测试脚本怎么写更好:框架

// __tests__/user.test.ts
import { FuncWarpper } from '@faasjs/test'; // FaasJS 对云函数的测试用封装
import { Sql } from '@faasjs/sql'; // 引入 Sql 插件
import knex from 'knex'; // 引入 knex 插件

// FaasJS 使用 Jest 做为测试框架
describe('user', function () {
  let func: FuncWarpper;

  beforeEach(async function () {
    // 生成云函数
    func = new FuncWarpper(require.resolve('../user.func') as string);
    
    // 为了便于测试脚本中对数据库各类操做,咱们把 sql 插件实例放个快捷方式在 func 对象上
    func.sql = func.plugins[0] as Sql;
    
    // 因为数据库链接是在 mount 阶段生成的,所以这里先 mount 一下
    await func.mountedHandler();
    
    // 建表
    await knex({
      client: func.sql.adapterType
    })
      .schema
      .connection(func.sql.adapter!.pool)
      .dropTableIfExists('users')
      .createTable('users', function (t) {
        t.integer('id').notNullable();
        t.string('name').notNullable();
      });
  });

  test('should work', async function () {
    // 插入假数据
    await knex({
        client: func.sql.adapterType
      })
      .from('users')
      .connection(func.sql.adapter!.pool)
      .insert({
        id: 1,
        name: 'hi'
      });
    
    // 调用云函数
    const res = await func.handler();
    
    // 检查返回结果是否符合预期
    expect(res.length).toEqual(1);
    expect(res[0].id).toEqual(1);
    expect(res[0].name).toEqual('hi');
  });
 });
复制代码

这里留一个小问题:当多个云函数都须要调用这个数据表时,如何封装比较好呢?(答案见后文)。less

适时分库,下降耦合

随着业务增加,必然会遇到数据种类和数量愈来愈多的状况,若是大量的云函数都链接到一个数据库,必然会对该数据库形成较大的压力,因此建议在开发到必定程度时,提早进行分库操做,对数据和代码进行解耦。async

FaasJS 的文件夹结构自然支持分库,假设咱们把 users 表和 orders 分拆为两个数据库,则只需将它们分别放在两个不一样的文件夹里,每一个文件夹里独自配置各自的 faas.yaml 便可。

我在 Github 上的示例代码包括了如下最佳实践示例:

  • 基于 Knex 和 TypeScript 定义共用数据表
  • 基于文件夹来分库分业务

点击连接前往: github.com/faasjs/exam…

最后,在使用中遇到任何问题,欢迎关注微信公众号(寂静小站)反馈交流或加入 QQ 群(772109193)一块儿讨论交流:

相关文章
相关标签/搜索