kong框架的插件(二)

        本节主要讨论kong插件中如何与数据库进行交互。

        kong支持的数据库有postgreSql和Cassandra这两种数据库。由于本人没有接触过Cassandra数据库,所以本文所使用的数据库为postgreSql。

        kong在持久化层提供了一个抽象,允许开发者操作自定义实体。这些实体对象称为DAO(Data Access Object)。

一、kong.db对象

        kong网关中核心组件的实体是service、route、consumer和plugin。所有这些实体都可以通过kong.db.*全局单例对象访问,代码清单见代码:

local services_dao= kong.db.services
local routes_dao = kong.db.routes
local consumers_dao = kong.db.consumers
local plugins_dao = kong.db.plugins

        用户自定义插件中的实体也可以通过kong.db.*全局单例对象访问。

二、DAO对象API

        DAO对象负责操作存储在数据库中的表数据。这些数据与kong的实体对象一一对应。kong底层支持的数据库都遵循一套相同的接口。

        插入service实体或者插件实体对象的操作非常简单,例如:

local inserted_service,err = kong.db.services:insert({
    name = "mockbin",
    url = "http://mockbin.org",
})

local inserted_plugin, err = kong.db.plugins:insert({
    name = "key-auth",
    service_id = {id = inserted_service.id},
})

kong自带的key-auth插件是一个很好的例子来展示DAO如何被使用的,key-auth插件

三、自定义实体

        用户如果想要在插件中使用自定义实体,首先需要根据情况定义一个或者多个实体。数据格式为Lua Table,其中描述了自定义实体的相关信息,包括实体的字段名称、数据类型等。实体的配置项与插件配置中的配置项有些类似,但实体的配置项多了一些额外的元信息,例如实体的主键。实体配置项在daos模块中定义。下面是kong的key-auth插件的daos模块定义:

local typedefs = require "kong.db.schema.typedefs"

-- 该插件会自动生成一个名为keyauth_credentials的DAO对象
return {
  {
    ttl = true,   --用于过期自动删除:
    primary_key = { "id" },
    name = "keyauth_credentials",     --数据库中对应的表
    endpoint_key = "key",
    cache_key = { "key" },
    workspaceable = true,    --根据命名空间进行隔离(支持多租户)
    admin_api_name = "key-auths",
    admin_api_nested_name = "key-auth",
    fields = {
      { 
        -- 主键
        id = typedefs.uuid 
      },
      { 
        -- 创建时间的时间戳,精确到秒
        created_at = typedefs.auto_timestamp_s 
      },
      { 
        -- 消费者ID作为外键
        consumer = { 
            type = "foreign", 
            reference = "consumers", 
            required = true, 
            on_delete = "cascade", 
        }, 
      },
      { 
        -- 唯一的键值
        key = { 
            type = "string", 
            required = false, 
            unique = true, 
            auto = true 
        }, 
      },
      { tags = typedefs.tags },
    },
  },
}

主要的属性描述信息如表1所示:

顶层属性的描述信息
名称类型是否必填描述
namestring        DAO对象的名称(kong.db.[name])
primary_keytable        实体主键,大多数情况下kong插件实体时使用UUID作为主键,主键名为id。插件实体也支持复合主键。
endpoint_keystring        在Admin API中作为备用标识符的字段,例如将endpoint_key设置为key,对于某个实体id=123,key="foo",则/keyauth_credentials/123和/keyauth_credentials/foo这两条URL是等价的。
cache_keytable          用于生成cache_key所需的字段
generate_admin_apiboolean        是否自动生成Admin API,默认情况下会为所有DAO对象生成Admin API,包括自定义的DAO对象。如果想要为DAO对象创建完全自定义的Admin API,或者想要完全禁用自动生成功能,可以将该项设置为false
admin_api_namestring        当启用generate_admin_api功能时,系统默认会使用name属性自动生成Admin API端点
admin_api_nested_namestring        与admin_api_name属性类似
fieldstable     是          实体中每个字段的描述信息

        我们在定义实体对象时,可以通过添加属性对每个实体对象的字段做约束。在使用DAO实体插入或者更新这些实体时,会先检查信息,如果输入的内容不符合规范,则返回错误。typedefs变量中包含大量实用的类型定义和别名字段,例如主键字段中最常用的typedefs.uuid属性或者created_at字段中较为常用的typedefs.auto_timestamp_s属性。表2罗列了一些常用的字段属性。

常用的字段属性
名称类型是否必填描述
typestring        字段类型,支持一下标量类型:string、integer、number、boolean;还支持一下复合类型:array、 recode、set;除此之外,还可以取foreign:表示外键关系;
default  any         字段默认值。当该值为空时,会自动填充Lua语言中的默认值,而非数据库中的默认值。
requiredboolean        字段是否必填,当设置为true时,输入是缺少该字段会抛出错误。
uniqueboolean        字段是否唯一,当设置为true时,如果另一个实体已经存在会抛出错误。
autoboolean        是否自动填充字段,当type==“uuid”时,该字段会填充UUID;当type="string"时,该字段会填充随机字符串。如果字段名为created_at或者update_at,该字段将在插入或更新实体时填充当前时间。
referencestring        当type是foreign类型时,该地段时必填的,且字段值必须时已经存在的schema名称。
on_deletestring        当type是foreign类型时,该地段是必填的。该字段只是了当实体关联的外键对象被删除时执行什么操作。其有三个可选值,cascade(删除所有从属实体)、null(将从属实体的外键字段值设置为null)、restrict(执行操作出现错误)

        更多的字段属性可以参见:typedefs.lua

四、DAO实体API

1、查询操作

        查询操作模板语句:

local entity, err, err_t = kong.db.<name>:select(primary_key)

        该语句会在数据库中查询对象并返回,可能会出现三种情况。

  • 通过主键找到对应的实体,实体对象以Table格式返回。
  • 查询过程中发生了错误,比如数据库连接丢失,此时entity参数返回nil,err参数返回描述错误原因的字符串,err_t参数也返回错误信息,但数据格式是Table。
  • 没有找到对应的实体,也没有发生错误,entity参数返回nil,不返回错误信息。
   2、遍历操作

        遍历操作模板语句:

for entity, err on kong.db.<name>:each(entities_per_page) do
    if err then
        ...
    end
    ...
end

        该方法可以创建分页请求,遍历数据库中的所有实体,entities_per_page参数控制每页返回的实体数,默认为100。每次迭代时,entity参数都会返回一个新对象。当参数错误时,err参数会填充错误内容。推荐读者在执行遍历操作时先检查错误信息,然后再执行自己逻辑。

3、插入操作

        插入操作模板语句:

local entidy, err, err_t = kong.db.<name>:insert(<value>)

        执行插入操作后,返回值包括三种情况:

  • entity:返回插入实体的副本或者nil。
  • err:返回错误信息,类型为字符串。
  • err_t:返回错误信息,类型为Table。
4、更新操作

        更新操作模板语句:

local entity,err,err_t = kong.db.<name>:update(primary_key, <values>)

        执行更新操作的前提时系统可以找到与主键对应的实体值。执行更新操作后,返回值包括三中情况:

  • entity:返回插入实体的副本或者nil。
  • err:返回错误信息,类型为字符串。
  • err_t:返回错误信息,类型为Table。
5、更新或插入操作

        更新或者插入操作模板语句:

local entity, err, err_t = kong.db.<name>:upsert(primaty_key,<values>)

        更新或插入操作融合了插入和更新两个操作。当primary_key对应的实体存在时,其类似于更新操作;当primary_key对应的实体不存在时,其类似于插入操作。

6、删除操作

        删除操作模板语句:

local ok, err,err_t = kong.db.<name>:delete(primary_key)

在执行删除操作后,

返回值包括三中情况:

  • ok:表示删除指定实体成功。
  • err:返回错误信息,类型为字符串。
  • err_t:返回错误信息,类型为Table。

五、migration模块

        开发者在定义完实体后,还需要创建migration模块。kong启动时会根据用户自定义的migration模块创建数据库表。

        migration模块的文件目录结构如下:

  <plugin_name>
├── migrations
│   ├── init.lua
│   └── 000_base_header_plugin.lua
└── schema.lua

        init.lua脚本的作用,根据kong官方文档的说明:

If there is no init.lua file inside already, you should create one. This is where all the migrations for your plugin will be referenced.

如果migration目录中没有init.lua文件,则你需要创建一个。这是您的插件的所有迁移将被引用的地方。

        kong框架执行插件数据库脚本是从init.lua脚本开始的,根据kong官方文档的说明,init.lua脚本为kong指明哪些数据库脚本需要被执行。例如,acl插件migration模块中的init.lua脚本:

return {
  "000_base_acl",
  "002_130_to_140",
  "003_200_to_210",
  "004_212_to_213",
}

        该脚本指明acl插件的数据库脚本为000_base_acl.lua、002_130_to_140.lua、003_200_to_210.lua和004_212_to_213.lua共四个脚本文件。

        当需要对插件进行迭代升级时,切记不能已经发布的迁移文件中进行修改,必须重新创建新的迁移文件(编写迁移文件做好做到可重复执行)。虽然没有严格的规则来命名迁移文件,但有一个惯例,即第一个文件以000为前缀,下一个文件以001为前缀,以此类推。

        迁移文件的语法规范示例:

-- `<plugin_name>/migrations/000_base_my_plugin.lua`
return {
 postgres = {
   up = [[
     CREATE TABLE IF NOT EXISTS "my_plugin_table" (
       "id"           UUID                         PRIMARY KEY,
       "created_at"   TIMESTAMP WITHOUT TIME ZONE,
       "col1"         TEXT
     );

     DO $$
     BEGIN
       CREATE INDEX IF NOT EXISTS "my_plugin_table_col1"
                               ON "my_plugin_table" ("col1");
     EXCEPTION WHEN UNDEFINED_COLUMN THEN
       -- Do nothing, accept existing state
     END$$;
   ]],
 }
}

-- `<plugin_name>/migrations/001_100_to_110.lua`
return {
 postgres = {
   up = [[
     DO $$
     BEGIN
       ALTER TABLE IF EXISTS ONLY "my_plugin_table" ADD "cache_key" TEXT UNIQUE;
     EXCEPTION WHEN DUPLICATE_COLUMN THEN
       -- Do nothing, accept existing state
     END;
$;
   ]],
   teardown = function(connector, helpers)
     assert(connector:connect_migrations())
     assert(connector:query([[
       DO $$
       BEGIN
         ALTER TABLE IF EXISTS ONLY "my_plugin_table" DROP "col1";
       EXCEPTION WHEN UNDEFINED_COLUMN THEN
         -- Do nothing, accept existing state
       END$$;
     ]])
   end,
 }
}

        每个策略都包含两端内容:up和teardown。

  • up是原始SQL语句的可选字符串。在执行kong migrations up时执行这些语句。
  • teardown是一个可选的Lua函数,它接受一个connector参数。connector参数可以调用查询方法来执行SQL查询。迁移完成后会触发teardown。

        我们建议所有非破坏性操作,例如创建新表和添加新记录,都在up阶段完成。我们建议在teardown阶段上执行破坏性操作,例如删除数据、更改行类型和插入新数据。

        所有SQL语句的编写都应该使它们尽可能具有可重入性。例如,使用DROP TABLE IF EXISTS而不是DROP TABLE,使用CREATE INDEX IF NOT EXIST而不是CREATE INDEX,等等。如果迁移由于某种原因失败,那么修复问题后重新执行指令即可。

参考资料:

  1. 《Kong网关 --入门、实战与进阶》
  2.  Kong官方文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值