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)一塊兒討論交流:

相關文章
相關標籤/搜索