API网关Kong学习笔记(十三): 向数据库中插入记录的过程分析

Tags: kong 

目录

说明

向数据库中插入记录的时候,会进行插入校验,校验过程会用到schema中定义的类型以及校验函数。这里在数据库操作封装的基础之上,分析kong/dao/dao.luaDAO:insert()的实现。

相关笔记

2019-05-06 16:28:56:kong 1.1.x有了一个重大变换,实现了db-less模式,可以不使用数据库了,见笔记二十六:查看全部笔记如果是刚开始学习kong,直接从1.x开始,0.x已经不再维护,0.15是0.x的最后一个版本。

前19篇笔记是刚开始接触kong时记录的,使用的版本是0.14.1,当时对kong一知半解,笔记比较杂乱。第二十篇开始是再次折腾时的笔记,使用的版本是1.0.3,笔记相对条理一些。

从0.x到1.x需要关注的变化有:

  1. 插件全部使用pdk
  2. 0.x中不鼓励使用的特性都被移除了;
  3. 全部使用kong.db,以前独立的dao彻底清除,代码简洁清晰了。

插入校验

DAO:insert()中写入之前的操作的是校验:

-- kong/dao/dao.lua
function DAO:insert(tbl, options)
  options = options or {}
  check_arg(tbl, 1, "table")
  check_arg(options, 2, "table")

  local model = self.model_mt(tbl)    
  local ok, err = model:validate {dao = self}   -- 校验 ---
  if not ok then
    return ret_error(self.db.name, nil, err)
  end
  ...

校验使用的model是用self.model_mt()方法创建的,要去找到model_mt()的方法。

存放了大部分dao对象的DAO是用kong/dao/factory.lua中的function _M.new(kong_config, new_db)创建的。

-- kong/dao/factory.lua
function _M.new(kong_config, new_db)
...
  load_daos(self, schemas, constraints)
...

load_daos()中创建了一个个具体的dao:

-- kong/dao/factory.lua
...
local ModelFactory = require "kong.dao.model_factory"
...
local function load_daos(self, schemas, constraints)
  ...
  for m_name, schema in pairs(schemas) do
    self.daos[m_name] = DAO(self.db, ModelFactory(schema), schema,
                            constraints[m_name])
  end
  ...

需要注意第二个参数ModelFactory(schema),它就是要找的model_mt():

-- kong/dao/dao.lua
-- @param model_mt The related model metatable. Such metatables contain, among other things, validation methods.
function DAO:new(db, model_mt, schema, constraints)
  self.db = db
  self.model_mt = model_mt
  self.schema = schema
  self.table = schema.table
  self.constraints = constraints
end

ModelFactory()使用传入的schema构建了一个table,并为这个table的设置了包含validate(opts)方法的元表:

-- kong/dao/model_factory.lua
local validate = schemas_validation.validate_entity
return setmetatable({}, {
  __call = function(_, schema)
    local Model_mt = {}
    Model_mt.__meta = {
      __schema = schema,
      __name = schema.name,
      __table = schema.table
    }
    ...
    function Model_mt:validate(opts)
      local ok, errors, self_check_err = validate(self, self.__schema, opts)
      if errors ~= nil then
        return nil, Errors.schema(errors)
      elseif self_check_err ~= nil then
        -- TODO: merge errors and self_check_err now that our errors handle this
        return nil, Errors.schema(self_check_err)
      end
      return ok
    end
    ...
    return setmetatable({}, {
      __call = function(_, tbl)
        local m = {}
        for k,v in pairs(tbl) do
          m[k] = v
        end
        return setmetatable(m, Model_mt)
      end
    })
  end
})

Model_mt:validate()使用的validate()方法在kong/dao/schemas_validatio.lua中实现,用schema中定义的每个filed对输入的数据进行校验:

--kong/dao/schemas_validatio.lua
function _M.validate_entity(tbl, schema, options)
  ...
  for tk, t in pairs(key_values) do
      ...
      for column, v in pairs(schema.fields) do
        if t[column] ~= nil and t[column] ~= ngx.null and v.type ~= nil then
          local is_valid_type
          -- ALIASES: number, timestamp, boolean and array can be passed as strings and will be converted
          if type(t[column]) == "string" then
            if schema.fields[column].trim_whitespace ~= false then
              t[column] = utils.strip(t[column])
            end
            if v.type == "boolean" then
              local bool = t[column]:lower()
              is_valid_type = bool == "true" or bool == "false"
              t[column] = bool == "true"
            elseif v.type == "array" then
              is_valid_type = validate_array(v, t, column)
            elseif v.type == "number" or v.type == "timestamp" then
              t[column] = tonumber(t[column])
              is_valid_type = validate_type(v.type, t[column])
            else -- if string
              is_valid_type = validate_type(v.type, t[column])
            end
          else
            is_valid_type = validate_type(v.type, t[column])
          end

          if not is_valid_type and POSSIBLE_TYPES[v.type] then
            errors = utils.add_error(errors, error_prefix .. column,
                    string.format("%s is not %s %s", column, v.type == "array" and "an" or "a", v.type))
            goto continue
          end
        end
   ...      

可以看到支持的类型有stringbooleanarraynumbertimestamp

field的成员都有:

v.type
v.default
v.immutable 
v.enum
v.regex 
v.schema
v.required
v.dao_insert_value
v.func

Model_mt:validate()还会调用schema的self_check()进行检查:

--kong/dao/schemas_validatio.lua
function _M.validate_entity(tbl, schema, options)
  ...
  for tk, t in pairs(key_values) do
      ...
      if errors == nil and type(schema.self_check) == "function" then
        local nil_c = {}
        for column in pairs(schema.fields) do
          if t[column] == ngx.null then
            t[column] = nil
            table.insert(nil_c, column)
          end
        end
        local ok, err = schema.self_check(schema, t, options.dao, options.update)
        if ok == false then
          return false, nil, err
        end
        ...
      end

kong/dao/schemas中有的schema实现了self_check(),例如kong/dao/schemas/plugins.lua

--kong/dao/schemas/plugins.lua
return {
  table = "plugins",
  primary_key = {"id", "name"},
  cache_key = { "name", "route_id", "service_id", "consumer_id", "api_id" },
  fields = {
    ...
    config = {
      type = "table",
      schema = load_config_schema,
      default = {}
    },
    ...
  },
  self_check = function(self, plugin_t, dao, is_update)
    if plugin_t.api_id and (plugin_t.route_id or plugin_t.service_id) then
      return false, Errors.schema("cannot configure plugin with api_id " ..
                                  "and one of route_id or service_id")
    end

    -- Load the config schema
    local config_schema, err = self.fields.config.schema(plugin_t)
    ...   
    if config_schema.self_check and type(config_schema.self_check) == "function" then
      local ok, err = config_schema.self_check(config_schema, plugin_t.config and plugin_t.config or {}, dao, is_update)
     ...
  end
}

需要注意的是用self.fields.confg.schema()创建的config_schema也可能实现了self_check。self.fields.confg.schema是函数load_config_schema(),它加载了插件目录中的schema。

-- kong/dao/schemas/plugins.lua
local function load_config_schema(plugin_t)
    ...
    local loaded, plugin_schema = utils.load_module_if_exists("kong.plugins."
                                    .. plugin_name .. ".schema")
    if loaded then
      return plugin_schema
    ...

插入记录

校验通过之后,使用db的insert方法插入数据:

-- kong/dao/dao.lua
function DAO:insert(tbl, options)
  ...
  local res, err = self.db:insert(self.table, self.schema, model, self.constraints, options)
  if not err and not options.quiet then
    if self.events then
      local _, err = self.events.post_local("dao:crud", "create", {
        schema    = self.schema,
        operation = "create",
        entity    = res,
      })
      if err then
        ngx.log(ngx.ERR, "could not propagate CRUD operation: ", err)
      end
    end
  end
  return ret_error(self.db.name, res, err)

需要注意的是,如果dao对象中有events,插入之后要向dao:crud频道中发出一个create事件。


kong

  1. API网关Kong学习笔记(二十六): Kong 1.1引入db-less模式,无数据库部署
  2. API网关Kong学习笔记(二十五): 重温 kong ingress controller
  3. API网关Kong学习笔记(二十四): 在kubernetes中启用kong的插件
  4. API网关Kong学习笔记(二十三): Kong 1.0.3的plugin/插件机制的实现
  5. API网关Kong学习笔记(二十二): Kong 1.0.3源代码快速走读
  6. API网关Kong学习笔记(二十一): Kong的开发环境设置(IntelliJ Idea)
  7. API网关Kong学习笔记(二十): Kong 1.0.3的安装部署和与Kubernetes的对接
  8. API网关Kong学习笔记(十九): Kong的性能测试(与Nginx对比)
  9. API网关Kong学习笔记(十八): Kong Ingress Controller的CRD详细说明
  10. API网关Kong学习笔记(十七): Kong Ingress Controller的使用
  11. API网关Kong学习笔记(十六): Kong转发请求的工作过程
  12. API网关Kong学习笔记(十五): KongIngress的定义细节
  13. API网关Kong学习笔记(十四): Kong的Admin API概览和使用
  14. API网关Kong学习笔记(十三): 向数据库中插入记录的过程分析
  15. API网关Kong学习笔记(十二): 插件的目录中schema分析
  16. API网关Kong学习笔记(十一): 自己动手写一个插件
  17. API网关Kong学习笔记(十): Kong在生产环境中的部署与性能测试方法
  18. API网关Kong学习笔记(九): Kong对WebSocket的支持
  19. API网关Kong学习笔记(八): Kong Ingress Controller的实现
  20. API网关Kong学习笔记(七): Kong数据平面Plugin的调用与实现
  21. API网关Kong学习笔记(六): Kong数据平面的事件、初始化与插件加载
  22. API网关Kong学习笔记(五): 功能梳理和插件使用-安全插件使用
  23. API网关Kong学习笔记(四): 功能梳理和插件使用-认证插件使用
  24. API网关Kong学习笔记(三): 功能梳理和插件使用-基本使用过程
  25. API网关Kong学习笔记(二): Kong与Kubernetes集成的方法
  26. API网关Kong学习笔记(一): Nginx、OpenResty和Kong入门,基础概念和安装部署
  27. API网关Kong学习笔记(零): 使用过程中遇到的问题以及解决方法

推荐阅读

Copyright @2011-2019 All rights reserved. 转载请添加原文连接,合作请加微信lijiaocn或者发送邮件: [email protected],备注网站合作

友情链接:  系统软件  程序语言  运营经验  水库文集  网络课程  微信网文  发现知识星球