开源项目 开源一个超酷的 Restful API 自动生成器:Kaola

ylt · 2017年07月21日 · 最后由 williamhatch 回复于 2018年08月25日 · 23865 次阅读

项目的地址是: https://github.com/yuanxinyu/kaola

0. 什么是 Kaola

Kaola(考拉,英文 koala)是一个全自动的 restful api 代码自动生成系统。给定一个数据库,只需要配置好数据库连接,koala 可以通过扫描数据库自动生成全套的 restful api 的后端代码。通过预先约定好的接口调用规范,前端就可以直接开发应用系统了。

1. 为什么开发 Kaola

类似 koala 的代码生成系统之前也有很多,比如 rails 自带的 scaffold 功能,以及更完善的 Active Admin/Rails Admin 等。但是这些系统都有两点不符合要求:

  1. 这些系统都是一套完整的系统,从后端到页面都一次性生成。实际的情况是,后端的接口比较通用,而界面和操作流程往往需要深度的定制。修改这些系统自动生成的页面是很困难的。
  2. 这些系统往往都是只支持单表 CRUD 操作,对多表之间的关联操作支持不够。

所以我开发了这套 koala 系统,它的主要特点是:

  1. 只提供后端的 restful api 接口,前端的代码还是需要每个应用自己编写,采用前后端分离架构;
  2. 接口不仅支持常规的单表 CRUD 操作,还自动支持多表关联(一对多、多对多、自引用的树形结构),支持复杂的查询(分页、排序、计数、索引)、支持批量操作、以及拥有一套透明高效的缓存体系。

当然,还有一点很重要的,就是定义了一套 restful 的基于约定的接口协议。基于这套约定的协议,前端程序员不需要后端提供繁琐的不一致接口文档,可以轻松上手开始使用这套接口。

2. Kaola 的接口协议约定

Kaola 生成的 Api 接口是基于 http 的 web 接口,URL 的命名基本沿用 rails 框架的命名约定,其中的表名都是复数形式。基本的 CRUD 接口的约定如下:

操作 HTTP Method URI
获取列表数据 GET /表名 (.:format)
添加新数据 POST /表名 (.:format)
修改已有数据 PUT /表名/:id(.:format)
查看已有数据 GET /表名/:id(.:format)
删除已有数据 DELETE /表名/:id(.:format)

这只是对单表资源的 CRUD 操作,koala 针对下列情况也定义了一套的 Restful 规范:

  1. 查询/分页/排序的支持。标准 Restful 接口只有一个列表的接口,对查询相关的功能没有约定,koala 自行扩展了一套约定。
  2. 批量操作的支持。Restful 接口默认只支持单个资源的操作,而实际的业务场景中经常需要有批量操作的需求,比如商品的批量上架、数据的批量删除等。
  3. 有关联的数据表的支持。Restful 只支持单个资源的 CRUD,而实际业务中经常有主子表的级联保存,关联表的查询等。

查询的约定

Kaola 的查询参数一一对应到数据库中的字段,格式是通过把 json 格式的查询参数扁平化得来的。比如下面的查询条件,

{
    "s" : {
        gender : 'm',
        "like" : {
            name : 'b'
        }
    },
    "order" : "id asc"
}

它的含义是查找所有 gender 等于'm'并且 name 包含'b'的记录,按'id asc'排序,扁平化以后就是:

s[gender]=m&s[like[name]]=b&order=id asc

如果表的名字是‘users’,那么请求‘users.json?s[gender]=m&s[like[name]]=b&order=id+asc’就可以得到所有符合条件的 json 格式的数据。

针对查询/分页/排序/批量操作/关联表操作/导入导出等的具体的约定(前端开发者需要细看)可以参考:

3. 开始使用 Kaola

Kaola 是基于 ruby on rails 开发的,主要在 Mac 和 Linux 操作系统下完成开发 ,数据库使用的是 mysql。如果你的操作系统是 windows,或者数据库不是 mysql,大体上是兼容的,但可能会碰到问题。

  1. 安装 ruby2.2 以上版本,如何安装可参考这个链接。安装完成 ruby 以后,在命令行运行”gem install bundle”以安装 bundle;

  2. 下载 koala 的代码,在项目根目录运行“bundle install”安装依赖的第三方库;

  3. 配置数据库连接,具体参考数据库配置

  4. 在项目的根目录运行“./autogen.sh”,自动生成所有的后端 api 代码;

  5. 启动 api 服务器,在开发环境下就是运行“rails server”

然后打开浏览器访问这个链接就可以看到生成的所有接口了。在开发环境下,koala 除了 api 接口,也提供完整的 CRUD 的 html 页面(其实就是 rails 默认的 scaffold 生成的页面)。在发布环境下,只有接口调用可以访问,基本就是以".json"结尾的 url 访问。

4. 实现原理

扫描数据库实现所有单表的 CRUD 功能接口,这个不难实现。比较难的是怎么处理数据关联关系。数据关联关系主要有三种:一对多,一对一,多对多。Kaola 在实现关联关系的时候,分两个阶段实现的。

识别一对多关系

第一个阶段,在 2016 年项目最初开发的时候,只支持一对多关系。一对一关系是一对多关系的特例,而多对多关系则可以表示为两个一对多关系,所以只支持一对多关系也不算严重的缺陷。

那么如何自动发现数据库所有的一对多关系呢,主要通过三种方法:

  1. 通过数据库的外键。如果 A 表有一个外键指向 B 表,那么 B 表和 A 表就是一对多关系。
  2. 通过命名约定。外键的命名约定采用 rails 默认的约定,外键的名字都是“表名单数_id”。同时对 rails 的约定有扩展,如果一张表里有多个关联到另外一张表的外键,命名规则是“前缀_表名单数_id”。所有符合命名约定的表,即使没有设置数据库层的外键,也自动建立一对多关系。

3.通过配置文件。有些遗留的数据库即没有配置外键,也不符合命名约定,那么在配置文件 custom_fk.txt 文件中配置好外键关系也可以。

关联关系是双向的,对于两个表 table1s 和 table2s,如果 table1 有一个字段 table2_id,那么 table1 是多方,table2 是一方,用 rails 来描述就是:

Table1 belongs_to table2
Table2 has_many  table1s

识别多对多关系

第二个阶段是 2017 年,kaola 开发完成接近一年的时候,有个项目组提出要多对多关系的支持,在压力下想明白了多对多关系。首先,rails 支持两种多对多关系:直接式的“has_and_belongs_to_many”和间接式的“has_many through”, 对应的例子见下图:

has_and_belongs_to_many

has_many through

Rails 已经不建议使用直接式的多对多关系参考,Kaola 也不支持这种方式。下面就是自动发现多对多关联的最关键一步:所有包含两个及以上外键(也包括命名约定/配置文件定义的外键)的表自动形成多对多关系。一张表有 2/3/4 个外键,分别会定义 1/2/6 个多对多关系,也就是给定 n 个外键,生产组合 C(2,n) 个多对多关系。

除了常规的三种关系,还有一种特殊的自引用关系:树形结构。树形结构最常见的例子有组织结构、产品类别等。为了存储树形结构,要求给定的表有一个指向自己的外键。外键值为空的节点是树的根节点。树形结构在 kaola 中被定义为一个指向自己的一对多关系。

关系的增删改

识别了这些常见的数据关联关系后,接下来的任务是如何支持对这些关系的增删改查操作。目前对关系数据的增删改还只支持一对多关系,不支持多对多关系。 由于 kaola 采用 json 格式提交数据,所以新增和修改有关系的数据是比较容易的,json 格式很容易支持用嵌套结构来表达一对多关系。比如单个新增的话,提交的是一个 hash 对象

{
    "表名单数": {id:id, key:value,...}
}

那么对于一次性提交主子表的数据,格式就是

{
    "主表单数": {id:id, key:value,...},
    "子表复数": [
        {id:id, key:value,...},
        {id:id, key:value,...}
    ],
    其它子表...
}

关系的查询

下一步是如何定义和实现关联关系的查询。Kaola 使用了面向对象语言的“.”操作符来表达主子表的关系,从而可以达到比 sql 语句更简单自然的查询表达式。比如有两张表:公司表 companies 和仓库表 warehouses,一个公司可以有 多个仓库,那么下面的查询

warehouses.json?s[company.name]='公司 A'

表示查询所有的 name 为‘公司 A’的公司所有的仓库。对应的 Sql 查询是:

select * from warehouses join companies on warehouses.company_id = companies.id where companies.name='公司 A'

关联表的查询支持所有单表查询的功能,包括等于/Like/日期/数值范围/枚举查询。 此外,针对一对多关系,还支持两种特殊的查询:Exists 查询(给定主表是否有子表数据)和树形结构查询(给定数据节点的所有深层嵌套子节点)。

具体的技术实现细节可以参考:

5. 案例

Kaola 主要的使用场合是内部 IT 系统的后端,比如各类管理后台、各类信息管理系统(CRM/SCM/ERP/HIS)等。

单语言案例:供应链系统

Kaola 最初的场景是用在一个供应链系统中。这个系统有几百张表,表间的关联复杂。应用 Kaola 后,整个后端代码量大大减少,所有增删改查的 api 都是自动生成的。最后只有三个文件,几百行代码是需要为供应链的逻辑定制的。这三个文件分别处理各类订单流水号生成逻辑、库存计算逻辑、订单流转逻辑。这些逻辑都是通过 rails 的数据库钩子实现的,不修改自动生成的代码,所以维护也很方便。比如下面就是序列号生成逻辑/订单流转逻辑的模版代码。

class ActiveRecord::Base
  before_validation :gen_seq

  def gen_seq
    #序列号生成逻辑
  end
end
class ActiveRecord::Base
  before_update :order_process

  def order_process
    #订单流转逻辑
  end
end

如果你的团队后端有 ruby 程序员,这是推荐使用 kaola 的方式,增删改查的 api 用 kaola 自动生成,其它的功能通过修改 kaola 的代码实现。

混合语言后端案例

如果你的团队后端没有 ruby 程序员,也可以使用 kaola 来帮助减少后端的开发工作量。通过采用前后端分离架构,前端开发其实不关心后端的 api 是用什么语言实现的。

我们在一个健康干预系统项目中采用了混合语言后端的案例。这个项目组都是 java 后端程序员,通过 kaola 来帮助 java 程序员完成增删改查的 api,而健康干预计划/健康报告生成等功能则是独立部署的 java 后端实现的。

6. 杂项

发布

由于 kaola 提供 api 接口能力太广泛,不能直接暴露在网络上。所以在发布的时候,需要部署在一个 api 网关后面,比如 Netflix/zuul 这样的网关。

License

Kaola 采用 MIT License, https://opensource.org/licenses/MIT

为何取名 Kaola

俗话说,懒惰是程序员的美德,能够让计算机自动完成的事情,就不要重复劳动了。取名 Kaola 是希望这套系统让程序员可以像考拉一样悠闲,同时 restful 也有宁静的含义,和考拉的形象比较匹配。

去年的技术分享

2016 年在高可用架构社区做的一次技术分享:

这种直接包装一下数据库需求,如果是 PostgreSQL 的话,直接上 PostgREST 是最爽的,全套解决方案都有了,性能还好

qinix 回复

去年开始写 kaola 的时候还真不知道 PostgREST,看来也是一种趋势。 不过用 rails 实现的好处是多种数据库都支持。

ylt 回复

测试的时候,mysql 和 oracle 都试过,没问题。其它 rails 支持的数据库,使用 kaola 应该问题也不大。 生产环境目前都是 mysql 数据库

这个好。那不是后端的要失业了,都去前端吧。

kaola 2016技术分享 链接是错的 https://ruby-china.org/topics/doc/share%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB.md

jasl 回复

谢谢提醒,kaola 2016 技术分享 链接已修复

不错,但是权限控制怎么做?

numbcoder 回复

权限确实是个麻烦的东西。 看了 PostgREST,它是直接利用数据库的角色进行权限控制,太死板。 目前还在构思一个优雅的内置解决方案。

numbcoder 回复

在目前几个内部项目中,kaola 都是配合其它 api 一起使用的。比如老白的供应链系统,需要对接京东/网易/顺丰等外部接口,这部分是一个独立部署的项目。

所以 url 鉴权是在更高一层的网关实现的,基于 Netflix/zuul。而细粒度的权限控制,则每个项目都是自行实现的。

CouchDB 似乎支持这个?

查询是不是还可以扩展一下,支持多层 and or 的嵌套就太棒了!

dfzy5566 回复

实现不难,关键是要有一个好的查询表达规范,符合前端程序员的直觉。你能用嵌套的 json 合理表达出你的需求,就可以实现这个功能。说实话,我认为 PostgREST 的查询语法很丑陋。

dfzy5566 回复

目前 kaola 支持的 and/or 嵌套查询是这种的 比如

s:{
    'f1,f2':v1,
    'f3':v2
}

表示 (f1=v1 or f2=v1) and (f3=v2), and/or 逻辑都是隐含的。如果要最外层用 or,也许更方便的是调用两次 api?

ylt 回复

吐槽一下 PostgREST 的查询语法设计:

  1. 查询字段没有独立的名称空间,所以它的 limit/order/select 等关键字会和要查询的字段名冲突
  2. 查询类型没有独立的名称空间,查询类型和查询内容混合,比如 age=eq.13, 这里 eq 和 13 分别是查询类型和查询内容,所以 PostgREST 难以支持 or 查询
  3. 不支持单个对象的获取
  4. 支持按搜索条件批量删除,容易误操作

建议提供英文版 README

@ylt 最近刚看到这个好东西。好像 github 的代码不全?作者还有最新的更新么或者联系方式,愿意一起探讨。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号