Gem 一个基于 Rails 的 RESTful API 框架: Lina

lyfi2003 · 2015年02月27日 · 最后由 vbgfnd 回复于 2015年04月24日 · 16353 次阅读
本帖已被管理员设置为精华贴

在最近这段日子 ( 2015 春节 ), 写了一个新的 API 框架:Lina, 主要特性如下:

  • 自动生成 APIDOC
  • Railser 零上手难度
  • 自校验参数与返回值,高效构建安全的 API 接口

为什么造一个轮子

前后端分离了,Rails 提供 API 的场景越来越多了。

Rails + jbuilder 写 API 不错,但维护 API 文档又是一个麻烦的事。

另外,grape 功能全面,可以帮助声明参数,但它自身一堆概念,学习曲线还是有的。

于是,Lina 就诞生了。

杀手锏

自动生成 APIDOC 是 Lina 的杀手锏,其妙招是编写如下控制器代码:

class PostsController < Lina::ApplicationController
  # def index
  define_action :index, {
    name: '获取所有文章',
    description: '这个接口用来获取所有文章, 参数及返回值请见下文',
    params: {
      properties: {
        created_order_by: {
          enum: [ :asc, :desc ],
          default: 'desc',
          description: '按创建时间正序或倒序排'
        }
      }
    },
    return: {
      type: 'array',
      items: {
        type: 'object',
        required: [:id, :name, :created_at],
        properties: {
          id: {
            type: 'integer',
            description: '文章ID',
          },
          name: {
            type: 'string',
            description: '文章标题',
          },
          created_at: {
            type: 'string',
            description: '创建时间, 格好的字符串'
          }
        }
      }
    }
  } do
    @posts = Post.order(created_at: params[:created_order_by].to_sym)
  end

end

如此,通过 define_action 的定义,Lina 就可以将参数声明与返回值输出为 APIDOC, 并且,参数声明还会帮你校验前端提交是否正确。

paramsreturn 是声明中最重要的部分,这种约束是来自于:JSON Schema, 十分简洁易读,并且非常完备。

除此之外,这份声明在后面可以用于构造表单,使得前端的 SimpleForm 成为可能。

除了控制器,Lina 继承了 Rails 绝大部分的优点,整合了 jbuilder, 使得编写 RESTful API 更得心应手。

我编写了一个完整使用 Lina 的项目示例,在这里:https://github.com/windy/lina_example

里面声明了两个 API, APIDOC 的生成效果你可以直接访问:http://linarb.org/apidoc 来查看。

内容较多,欢迎前去项目主站围观点赞:http://linarb.org

额外知识

附,Ruby 社区 RESTful API 框架优劣分析:

先说专注于 API 的框架

1. grape

优点

  • 最早一批出现的专注于 API 的框架
  • 拥有自身一套完整的概念,功能非常完备
  • intridea 开发维护,质量颇高

缺点

  • 优点即缺点,固执一套概念,使得与 Rails 很多组件整合都有坑
  • 因为是新概念,学习成本较高

2. rails-api

优点

  • 最早一批出现的专注于 API 框架
  • 基于 Rails, 与 Lina 类似,充分利用了 Rails 的特点,支持功能完备
  • 整合了一批 Rack 中间件,一定程度上提高了 API 响应效率

缺点

  • 出现的太早,更新比较慢,无法充分利用 Rails4.1 出现的独立 Engine 系统。
  • 事实上,rails-api 无法支持 APIDOC, 又未缺省支持 jbuilder, 使得开发起来与 Rails 无样

3. Lina

优点

  • 自动生成 APIDOC, 节省了大部分的维护成本
  • 自动校验参数,无须在控制器编写大量边界判断代码,可靠性提升
  • 基于 Rails, 充分利用 Rails 的优点,使得短时间内功能已经基本完备

缺点

  • 刚诞生,还有不少特性需要添加
  • 稳定性与质量还需要更多时间来测试

其他非专注于 API 的方案:

  1. Rails + jbuilder

    我个人非常喜欢,既简单又方便,有了 Lina, 可以继续沿用。

  2. sinatra

    非常轻巧的方案,适合于快速写一些简单的 API, 但对于大一点的项目,就远不如 Rails 方便了。

最后,欢迎关注 Lina 的发展:http://linarb.org

集成 doc 非常赞,之前到处找 gem 来解析 doc,提个建议 http://linarb.org/apidoc 没有响应式,没有模拟操作。

请问,如果要手动返回一个错误消息的 json 比如 {"error": "not recode yet"} 的时候,jbuilder 文件应该怎么写。是直接判断 @post 是否存在么

匿名 #3 2015年02月27日

:plus1: ,为什么叫 lina 呢?听着像 美女的名字 😄

#1 楼 @flowerwrong 响应式好主意,收下此建议。你另说的模拟操作是指什么?

#2 楼 @ywjno 如下:

#xx.json.jbuilder
if @post.blank?
  json.error 'not record yet'
else
  json.extract! @post, :id, :name 
end

#3 楼 @chanshunli 一个游戏角色名字,其实比较霸气的 😄

应该加入 cache 机制,虽然可以自己加,但是实际项目 cache 应用的好坏直接决定了整体性能

一直用 grape 等项目稳定后,再考虑使用 lina,谢谢楼主

#6 楼 @zeeler API 的 cache 我以为没有统一的方法,所以像 Rails 那样提供标准的 Rails.cache 方法即可。 #7 楼 @stephen 😄 不建议轻易切换,可以试着玩玩看,发现啥问题欢迎提 issues :)

#9 楼 @yesmeck 刚看到,这个项目很像 Lina, 它自己定义了一套 DSL 来声明参数,思路蛮像。一个区别是,它的定位是 Rails API 生成工具,Lina 的定位是一个 API 框架。另外,实际上做一套完备的 JSON 声明是非常麻烦 ( 我看了下,它定义了众多的 DSL, 难于记忆 ), JSON Schema 已经发展到 v4 草案了。我相信用 JSON Schema 来实现还是非常有优势的:

  1. JSON Schema 是用 JSON 来定义 JSON 数据,数据结构纯粹,只有 6 种。
  2. JSON Schema 是完备的,见这里的一个例子:http://json-schema.org/example2.html
  3. JSON Schema 是有标准的且不断发展的:http://json-schema.org/latest/json-schema-core.html

谢谢你的介绍。

出现的太早,更新比较慢,无法充分利用 Rails4.1 出现的独立 Engine 系统。

LZ 这里说的“Rails4.1 出现的独立 Engine 系统”是指什么?

这个不是很早就有吗?为什么说是 4.1 出现的。

:plus1:

很美的名字,很想念,呵呵

lina 生成的文档里没有响应样例,如果能把响应样例加上去,那就比较好了。

支持一下

#15 楼 @mogodb 每个中国人都认识一个叫 lina 的女孩就像每个美国人都认识一个叫 jack 的男人一样。

#16 楼 @kayakjiang 嗯,收到,#1 楼 也提了这个建议。接下来几个版本还会不断改善 APIDOC, 最后应该还要做到可以方便定制。

grape 需要额外维护一份文档,文档没有约定,导致最终伙伴们东一句西一句, 最终发展的结果 看文档成了最头疼的事 有空去玩玩楼主的 lina。 Ps lina 的字体能不能换一下 好丑啊... grape 看着多舒服 哈哈哈

#20 楼 @bxd602 grape 也可以自动生成文档,grape-swagger

#21 楼 @42thcoder 正想说这个。 用 grape+swagger 生成的文档可以直接用于 api test 页面也漂亮

#4 楼 @lyfi2003 用 api 之前总喜欢先试一下,心里有底。哈哈,类似谷歌的 restful 插件,做 api 请求。swagger 也有这个功能,只是 swagger-rails 在 rails 4.2 下出问题。

#20 楼 @bxd602 logo 还有很多改进空间,欢迎给个设计图 PR.

#24 楼 @flowerwrong 你说的是在线模拟测试吧,很多 API 都会有登录限制,或权限约束,还不如直接用一些 chrome 插件或者本地的方案比如:

所以,不太考虑这个特性。

我觉得 Grape 已经很好了 坑实在不能算多 而且代码写出来很好看 比这里一堆 Hash 好看多了

#26 楼 @iBachue Grape 确实已经非常好,不过有不少人比如我更喜欢 Rails 风格的 RESTful API 写法,Lina 便是另一个选择。

Hash 的方式不如 DSL 好看,但我没有定义像 Grape 那样定义 DSL, 正是因为表面的简单带来了复杂的 DSL 学习成本高,还不如直接找一个有官方维护的标准,这便是 JSON Schema. 最主要的是完备性。相信如果你看一个 更复杂的 JSON 的例子就明白了:http://json-schema.org/example2.html, 可以看看 Grape 中能否实现这个参数声明。

@lyfi2003 如果能这样写是不是更好看点?

desc(
  name: '获取所有文章',
  description: '这个接口用来获取所有文章, 参数及返回值请见下文',
  params: {
    properties: {
      created_order_by: {
        enum: [ :asc, :desc ],
        default: 'desc',
        description: '按创建时间正序或倒序排'
      }
    }
  },
  return: {
    type: 'array',
    items: {
      type: 'object',
      required: [:id, :name, :created_at],
      properties: {
        id: {
          type: 'integer',
          description: '文章ID',
        },
        name: {
          type: 'string',
          description: '文章标题',
        },
        created_at: {
          type: 'string',
          description: '创建时间, 格好的字符串'
        }
      }
    }
  }
)
def index
  @posts = Post.order(created_at: params[:created_order_by].to_sym)
end

#18 楼 @esseak 为何是这两个名字呢?还是泛指?你认识几个 lina?

#27 楼 @lyfi2003 好吧 但是我不得不表示 JSON Schema 的学习成本远远在 Grape 那几个简单的 DSL 之上,而且我记得 JSON Schema 标准还存在多个互不兼容的版本的问题,进一步提高了学习成本,而且 Ruby JSON Schema 库的出错提示不够友好 都没法直接用。

#2 楼 @ywjno 我这样用,直接写到 Controller 里面了……或者自己封装一个方法,因为错误就那些。

if @post
  render :show
else
  render status: :unprocessable_entity, json: {errors: "blalalalalalalalal"}
end

#31 楼 @iBachue Grape 非常 Nice, 这个我很认同。如果要讨论 JSON Schema 与 Grape 的 params DSL, 我要说,完备性太重要重要了,试想,如果你要定义一个 JSON 结构,结果 Grape DSL 支持不了怎么办?更何况,我个人认为对于参数自定义的 DSL 并不好用。

比如这个:http://json-schema.org/example2.html, 是否可以用 Grape DSL 定义出来?

而在正常情况下,也有可能遇到这种情况,比如返回值可能有多种 hash, 比如参数里还有子 hash, 子数组。

#33 楼 @lyfi2003 其实 Grape 的参数验证支持嵌套的 子 hash 子数组并不会成为太大的问题,你这个 example 我看过,里面关于 storage 的验证确实够变态的,用 Grape 的话只能自定义 Validator 才能做到(为了方便,我可以在这个 Validator 里调用 JSON Schema 来验证,这可以做成一个通用的 Validator),否则没有什么好的办法。不过我还是觉得为了完备性牺牲这么多不太划算,毕竟对于大部分 Case,还是简单的 DSL 更加好用点。

#34 楼 @iBachue 当然可以自己写个 validator, 但还要生成 APIDOC. Lina 就完全可以做到。实际上这个需求并非多变态,在处理返回值需求的时候,很经常会用到多值 ( 例如 JSON Schema 的 oneOf ), 因为有错误处理,用 Lina 之后,这种 APIDOC 会非常漂亮,而且可以帮助你检查返回值是否符合需求。

另外,Grape 非常不错,适用于纯 API 场景,但集成到 Rails 里总是感觉不舒服,Lina 更适合于 Railser 使用,它本质上就是 Rails + jbuilder 的加强版,既写页面很不错,又写 API 更好 ( Lina 的支持 ), 何乐而不为呢?

#35 楼 @lyfi2003 好吧,API Doc Grape 也有,但仅仅只是针对参数的 Doc,返回值不会有 Doc。但是说起返回值,看上去你依然在用 JSON Schema 做验证,这是为什么,验证返回值的正确性意义何在?

#36 楼 @iBachue 既然是完整的 API, 返回值要保证正确。验证返回值就很有必要,APIDOC 中也要体现出来。当然,这些校验都是可选的。

配置如下:

# lina config file
Lina.setup do |config|

  #是否开启返回值参数校验
  #config.return_check = true

  #返回值校验时使用的 json schema 版本, 更多: http://tools.ietf.org/html/draft-zyp-json-schema-04
  #config.return_json_schema_version = :v4
end

你不想关闭全局校验的话也可以直接用 return: {} 留空来达到与 Grape 一样的效果。

#37 楼 @lyfi2003 API 的性能至关重要,尽可能节省更多的开销吧。但是如果 return 留空也就没文档了不是么? 另外你的返回值文档和 jbuilder 文件位于不同的地方,这可能会使文档失去维护,如果放在一个文件里则会好一点。另外我留意到了#2 楼的建议,感觉你的方案不够优雅。其实很多 API 处理错误的代码是一样的,可以像 rescue_from 一样写在一起,所以他们的 schema 也理所当然的应该写在一起。

#38 楼 @iBachue 理念不太合,不多讨论谁优谁劣了。Lina 还有许多待完善的特性,届时应该会更强大。

2 楼在此,我只是想得到这种情况我该如何写 jbuilder 的而已。而且 api 还有很多校验比如每小时调用次数过多、没有权限等等。有些都是一些主动返回信息而不算得上是返回服务器异常。

另外已经在用该 gem 搭建一个只有一个方法的 api 了

还是比较喜欢 grape dsl 的方式 而且楼主提到 grape 的学习曲线我倒是完全没有感觉,很轻松就上手了,也没碰到什么坑,除了不允许我在 api method 以外修改 response 这点让我不愉快

#39 楼 @lyfi2003 lina 是用 vim 开发的么?

#42 楼 @mogodb 你关心的问题真奇怪,是用的 vim, 我一直都用的 vim, 而且是命令行版本。

我说 linadota 里火女的名字吧~

为啥 return 还有 require 或者 optional!

#48 楼 @Lucifer 根据 API 的需求而来:

  1. 不需要 return 声明及判断:Lina 中可以通过 配置关闭校验。
  2. return 中 比如 User, 如果它所属有一个 city 字段,用户未设置就没这个返回字段,你就可以设为 optional.

在 jbuilder 里使用 router 的 helper 方法 直接报错,root_path 指到了,"/apidoc/"

#50 楼 @chrishyman router 的 helper ? 不是很懂,能否将错误放在 github 的 issues 中:https://github.com/windy/lina/issues

我来看下。

好东西,太感谢了。

@lyfi2003 , 飞哥,apidoc 每次刷新,左侧的菜单都收起来,又要展开,非常非常不方便。

#53 楼 @pestd 嗯,好主意。我回头加一下。

The property '#/examcat_id' of type String did not match the following type: integer

有这样的错误,post 过来的数据是数字

#55 楼 @bigjoe Rails 参数进来的 ID 要用 string. Rails 为了安全,全部转成 string 了。

@lyfi2003 , 飞哥 ,请问,如果 api 需要做上传图片,那么 type 是啥呢?我用 object 报错: The property '#/logo' of type ActionDispatch::Http::UploadedFile did not match the following type: object

#57 楼 @pestd 这个临时可以用 { type: ActionDispatch::Http::UploadedFile } 来校验,不过应该可以用 type: 'object'

多谢这个反馈,我在下一个版本将其修正为 { type: 'object' }

@lyfi2003 , 用 ActionDispatch::Http::UploadedFile 或者 object 貌似都不行,我测试了。

如果用 ActionDispatch::Http::UploadedFile,错误是: The property '#/properties/logo/type' of type Class did not match one or more of the required schemas. The schema specific errors were: - anyOf #0: - The property '#/properties/logo/type' value ActionDispatch::Http::UploadedFile did not match one of the following values: array, boolean, integer, null, number, object, string - anyOf #1: - The property '#/properties/logo/type' of type Class did not match the following type: array

用 object,错误是: The property '#/logo' of type ActionDispatch::Http::UploadedFile did not match the following type: object

#59 楼 @pestd 嗯,着急的话,你可先把这个校验的属性去掉,在 description 里面写清楚这个上传文件的依赖即可。

我刚才研究了一下,这个问题还需要花些功夫才能解决。解决后我通知你。

@lyfi2003 ,如果返回的是 jsonp,那么 type 应该是 string 了吧?

render json: @result.to_json, :callback =>'callback'

飞哥,apidoc 的右边内容区域加个刷新按钮,点击只需刷右侧。另外,搞个首页,可以让配置一些通用的说明文字,比如作者、注意事项等。

#63 楼 @pestd :plus1: 好收到。

先加到 issues 里了,我闲一点的时候去处理。

又有个问题,本来已经用了 paperclip 的话,怎么接受图片?要自己另外实现吗?

#65 楼 @pestd 可以继续用,不影响的。跟上传文件的处理类似。

#51 楼 @lyfi2003 飞哥,你的帖子里把文字加粗,排版是怎么做到的?社区的发帖功能里貌似没有这些编辑工具。。。我 word 上编辑好的格式粘贴下来没了。。。。。。

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