Rails 同时需要 API 和 Web 页面的 Rails 项目,怎样结合 Grape 和 Rails?

dudu_zzzz · 2016年04月15日 · 最后由 wuguzaliang 回复于 2018年03月06日 · 6613 次阅读

问题是这样的,准备开发的应用会涉及 WEB 端和 APP 端,所以,打算用 Grape 做 api,rails 做 web 页面

但是这样,是不是意味着同样的业务逻辑需要在 Grape 和 Rails 里分别写一遍?或者干脆 WEB 端也前后分离,直接调用 API?

迷茫,有什么好的实践经验可以分享吗?

不要混用,就用 Rails。

可以参考 ruby-china 源码

如果你又要 api 又要 html view,我觉得就不要用 grape 了吧。。直接用 rails 的 json builder 吧= =controller 看上去会舒服很多。

ruby-china 可是有官方客户端的,这不是现成的吗

我们公司是 rails-api 写 api,web 端使用 reactjs + redux

建立前后分离,搞单页 APP

同样的业务逻辑需要在 Grape 和 Rails 里分别写一遍? 你们逻辑难道在 controller 里面?

我在极客学院上看了 Grape 和 rails 的集成,感觉挺好的,但是为什么我感觉这么做,都要多写一次结构呢?

#7 楼 @zhang_soledad 是不是最好把逻辑卸载 model 里面?

@hewe active_record 是充血模型 逻辑都放在对应的领域模型里 只有在过于复杂跨多个领域模型的时候才会写 service 但是逻辑都不会写在 controller 里面

不要用 Rails,应该前后端分离

最近刚好用 Rails+Grape 重写了公司之前用 PHP 写的 API,个人感觉 Grape 写起来还是不错的,还可以用 Swagger-UI 搭建可视化页面;具体可以参考 http://baya.github.io/2015/04/03/grape-api-%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5.html http://blog.elbowroomstudios.com/apis/ http://dev.classmethod.jp/server-side/ruby-on-rails/ruby-on-rails_create_grape_web-api/ 这些人的例子。

感谢各位! 决定前后分离,Rails+Grape 做 API!

#5 楼 @jicheng1014 请问用 rails-api 怎么做请求参数的校验?

#14 楼 @numbcoder rails 怎么做 rails-api 就怎么做呗 rails-api 是一个简化版的 rails 去掉了大多数关于 view 层的东西 https://github.com/rails-api/rails-api 之后现在 rails-api 已经被 merge 到 rails 里了

直接基于 rails 的 controller 做 api 也挺好的,可以看 rubygems 的源码,doorkeeper 做 oauth2

#15 楼 @jicheng1014 rails 做参数验证大多放到 model 层,这放到 API-Only 的项目可能不太实用,特别是一些 GET 请求。

#17 楼 @numbcoder 不知道这些教条主义是怎么起来的。

每天使用的 rubygems.org api 就是个 Rails 项目。

#17 楼 @numbcoder 只看过简单 JSON API 项目的人很难理解 Parameter Validation and Coercion 的重要性

#18 楼 @rei 纯 API 项目和传统的 Web 项相比是有很大区别的,传统 web 项目可以自己控制请求参数,而且可以自己在页面用 JS 等方式验证数据格式。而 API 项目却不同,调用 API 的环境比较复杂,可能是 Web 端、移动端,或者是其他应用程序,这种情况下你就很难保证 API 的使用者在使用 API 时会先对参数进行严格校验,这种情况无论对于安全性还是服务器资源的利用都是不利的

举个最简单的登录的例子

user = User.find_by_email(params[:email])

如果不先对参数 email 进行校验,而实际上它是一个非法的 email 格式,那么这次数据库查询几乎就是浪费。 我说的这种情况在你所强烈推荐的 rubygems.org 项目中到处都是,所以我并不认为它是一个好的可以参考的例子!

关于 API 的设计,我推荐这篇文章 http://mp.weixin.qq.com/s?__biz=MzA3NDM0ODQwMw==&mid=208060670&idx=1&sn=ce67b8896985e8448137052b338093e0&scene=21#wechat_redirect

#19 楼 @42thcoder #20 楼 @pathbox

#21 楼 @numbcoder 在 Web 项目服务端也不相信请求的参数。

#22 楼 @rei Rails 的一个核心思想是 CoC,所以在比如访问 /users/:id 页面的情况下,我相信大部分人写代码不会去验证参数 id 是否是 Integer 类型,而是直接 User.find(params[:id]),原因在于,大部分情况下用户访问 /users/23 这个页面都是通过链接点进去的,而不是自己手动输入,这种情况下 id 不存在或者格式有误的情况非常少

#21 楼 @numbcoder Web 项目,浏览器输入的 param 一样需要严格校验的,否则被恶意请求搞出各种问题。

互联网应用的准则之一,就是不要相信任何外部的输入。

#18 楼 @rei rubygems.org 是 Rails 写的 API 项目就能证明 Rails 适合写 API 吗?我遇到的很多人都认为 Grape 比 Rails 更适合写 API。

#25 楼 @nouse 这些人 Rails 用得不够熟练。

#26 楼 @rei

Present any piece of real code and we can ping pong on it. I enjoy talking about specifics and improving real code.

#27 楼 @42thcoder https://github.com/rubygems/rubygems.org

一个成功例子还不够?我倒没看见说 Grape 比 Rails 好有实例的。

Grape 跟 Rails 有个失败例子,之前 Ruby China 有个权限漏洞,原因是 Grape 跟 Rails 过滤参数不一致,导致 Mongodb 注入。

#28 楼 @rei 这是一个"成功"的例子么?我看出不来。麻烦举个 rubygems 项目里面的 ( 需求 ) 复杂 API 吧

#29 楼 @42thcoder 不成功你可以不用啊。让你觉得这个 API 很简单就是他们写得好。

#23 楼 @numbcoder 不知道你为何这么想,我猜你之前是写 java 或者 c++ 的。

#31 楼 @embbnux 呵呵 难道你觉得验证参数是很愚蠢的行为吗?

#17 楼 @numbcoder 额 我好像没 get 到你这的点呢。

校验是放在 model 里的,当然你要在 action 里做一次校验,没什么不好的。如果你是怕多查数据库的话,你可以加个 redis 限制 api 的访问就好了

我们的 rails 项目就是纯 api 的,效果还行

#30 楼 @rei ¯_(ツ)_/¯



#33 楼 @jicheng1014 说来说去都说得很虚,我举一个现实的场景吧:

需求

前端需要订单列表接口,根据属性做过滤,GET admin/orders.json .

订单的 schema 我们简化为:


#  title           :string(255)
#  code            :string(64)       not null
#  id              :integer          not null, primary key
#  state           :string(255)

产品的需求

  • 只允许查询状态是已结束和已关闭的 ( %w(closed done).include? state ), 后期随时扩展,默认是 closed
  • 如果要根据标题做查询,一定要填写编码。( code 和 title 两个参数要么都不传,要么一起传 )

前端开发和后端开发的约定:

  • 每页最多只能取 200 条记录 ( per_page < 200 )
  • id 一定要是整型,不允许 undefined

Grape 的做法

Grape 提供了一套 DSL 来做 Parameter Validation and Coercion. 上面的需求可以这样做:

params do
  optional :title, type: String, desc: '标题'
  optional :code, type: String, desc: '编码'
  optional :id, type: Integer, desc: 'ID'
  optional :state, type: String, values: %w(closed done), default: 'closed', desc: '状态'
  all_or_none_of :state, :code

  use :pager_params
end

Bonus

借助 Swagger, 上面那段代码可以转化为前端友好的文档。live demo: http://petstore.swagger.io/

Rails 的做法

裸写,具体位置可以放在 controller, 也可以放在 model 层。当然作为一个 Rails 用得不够熟练的码弱,还期待 LS 的指教。

Let's ping pong~ 👏

#33 楼 @jicheng1014 #34 楼 @embbnux Post/Put 的请求是可以放在 model 层检验,Get 请求的参数你们都不校验对吧? 我所强调的是安全性和性能,尽量把事情做到极致,如果你们觉得这两个都无所谓,那就争的没啥意思,毕竟价值观不一样。

#30 楼 @rei 请不要再拿 rubygems.org 这个项目为例来误导别人了,我前面已经举例说明了,它为什么不是一个好的例子

我说「吸烟有害健康」,你们却反驳说「张学良吃喝嫖赌活到了 103」,那我只能呵呵了

#34 楼 @42thcoder 的例子,我再来举一个 Get 请求,不验证参数导致的安全性问题

我曾经见过不少新手都是这么写的 API 的原本意图是是可以按 created_atupdated_at 排序来取数据

# PostsController
def index
  @posts = Post.order("#{params[:sort]} #{params[:order]}")
end

如果 API 的调用者,故意给 sort 参数传了一个未加索引的字段,或者是一个 text 类型的字段,可以看看你们的数据库还能撑多久

#35 楼 @42thcoder @numbcoder 说来惭愧,我们的项目在 get 的时候大多数情况没在查询之前做参数验证,觉得我们开放出来的查询都会有索引,所以传错值问题引起的性能损失不大。主要考虑的还是安全问题,比如不做校验,会带来哪些安全上的风险呢?

#37 楼 @numbcoder 这种写法肯定是不科学的

#35 楼 @42thcoder 这个帖子不错,讨论示例最直观。

感觉是各有所长啊。grape 可以不用写文档。

如果没有 web 单纯用 Rails 写 API 有点大炮打蚊子的意思,@rei 老大说那是你大炮用得不好 😊。个人认为 grape 简单,足够应付 API,楼上提到的参数验证还有结合 swagger 也是极好的。而 Rails 是个臃肿的 web 架构(原谅我不懂给它瘦身)Ruby 被“慢骂”的原因不正好是太多的误用吗,足够简单才好去避免误区。

#37 楼 @numbcoder #38 楼 @jicheng1014 根据 id 或者根据 email 来查找,通过验证格式来减少数据库查询,这个是否要做,完全看项目需求和编码规范。

但是 order by 或者其他查询直接依赖传入参数如果不做校验的话,不单单是潜在的性能 DDOS 问题,还有 sql 注入问题,这不是新手的问题,或者说是 rails、grape 的选择依据,完全是因为没有意识到这里的安全问题导致

#43 楼 @quakewang 我问大家 Rails 如何方便的做参数校验时,有人觉得我是奇葩,为什么会有这种想法,所以我才举出上面的例子。我并不是说 Rails 有安全性问题,我只是说大家很容易忽略 Get 请求的参数验证,事实上在 Rails 里做请求参数验证就是很不方便,上面已经有人举例子了。

#35 楼 @42thcoder RESTful api 关于 create/update api 数据校验是在 model 做的,关于 read/delete by id,这个并没有什么可说的。

你举的关于复合查询 api,我通常会选择引入 ranksack ( https://github.com/activerecord-hackery/ransack ) ,然后写一个 hash 的 filter 扩展方法,用起来是这样:

params.ransack_filter!(title: :cont, code: :eq, id: :eq, state: {predicate: :eq, values: [:closed, :done]})
Order.search(params)

不过不满足你例子里面的 dsl all_or_none_of,需要再做一些扩展,和 grape 的 dsl 相比,缺点是文档和代码不同步,优点是 2 行代码可以搞定(包括查询)

#43 楼 @quakewang 赞你的说法,不知道楼主在喷什么,也不能因为前面有参数验证就这么写,order("#{params[:sort]} #{params[:order]}") 这种不能容忍

#47 楼 @embbnux 用 46 楼提到的 rails_param ,我就这样写

def index
  param! :sort, String, in: %w(created_at updated_at), default: "created_at"
  param! :order, String, in: %w(asc desc),  default: "asc"

  @posts = Post.order("#{params[:sort]} #{params[:order]}")
end

请教一下能忍的写法是怎样的?

#48 楼 @numbcoder 我说的是尽量避免拼接 sql, 你这边 order 需求要那么多种情况的话,也只能这样。不过因为 order 的类型也就那么几个,用 include 是可以完全防住,其他的难道都能用 include 防住?最好还是这样 User.find_by(email: params[:email])

这个问题我一开始也纠结过。先分享下我现在的用法和项目背景 因为是纯 API 服务,所以

  1. API 使用 grape on rack 的方案,自己加一些配套的 gem 来完成类似 rails 的工具链
  2. 使用 rails 开发 admin

对于你这个的问题。我的想法是这样的: 分析 API 和 web 的比重,如果 API 比较多,超过 80%,那么我建议用 grape,只是 on rails 还是 on rack,这个再根据性能要求去分析,其实一般要求的并发 rails 完全可以 hold 住。要求再高的可以用 on rack。 如果 web 超过 80%,那么肯定是用 grape on rails 或者 rails 的方案更合适

我认为,grape 在写 API 方面比 rails 裸写或者 rails_param 的方式肯定是更优雅的,也更合适做 API 的服务。如果 API 比重大那么就可以考虑引入。对比到学习成本和开发受益,应该是会有收获。

关键还是用合适的工具去做合适的事情,这么一个宗旨

最后引用一个老帖算是抛砖引玉吧 https://ruby-china.org/topics/9765

我们最近的项目用 Rails Metal 结合 jbuilder 做 API,Rails 做后端的 CMS,感觉比用 grape 用得舒服多了。

@numbcoder 明白你的意思,单纯来讲只是觉得你给的例子很不靠谱,类似 email, CID, phone 这种验证完全可以通过 rails validation 来处理而且并不会产生查询,再者这种新手写的这种 query 做法 (stupid) 跟上面讨论的(选谁或者是否校验)优劣性是 2 个问题。

Grape 很好用,只是集成在 rails 中时,会调用一些没有用到的 rails 中间件,不过这个性能消耗可以接受,主要是要做好缓存

其实 Grape 那个 params 验证还是挺需要的,Ruby China 重新基于 Rails API 来改写了 API 以后,还是自己实现了一个类似的 params 验证机制(预言未来 Rails API 会自带这样的功能)

https://github.com/ruby-china/ruby-china/pull/612

然后再说 Grape 那种自动生成文档,其实也是看起来很美,还是太粗糙了,尤其是你没法写太多例子,不然代码里面全是一大堆一大堆的注释了,真正大型复杂的项目还是得用 https://apiary.io 这类工具。

写 rails-api 的时候用到过 apipie-rails(GitHub) 这个 gem, 参数验证和简单的文档生成都能胜任。

#55 楼 @karloku 试过,不好用,放弃了

看了这么多讨论貌大家都没有回复:是不是意味着同样的业务逻辑需要在 Grape 和 Rails 里分别写一遍?或者干脆 WEB 端也前后分离,直接调用 API?其实我也想知道答案,另外如果 web 和 app 项目只有 60% 业务逻辑可以公用又该如何选择呢?

#57 楼 @jayliud 当然不能都写一遍,我的做法是写一个 gems 然后封装这些 model,还在探索。

#58 楼 @ericguo 所以呼唤有此类项目经验的人来做些分享,不要抓着参数验证这块不放。。。业务逻辑移动和 web 即使同一个业务也可能有细微的不同

#48 楼 @numbcoder 这个写法不能容忍吗? 看不出有什么危险啊

#8 楼 @hewe 你看的是不是我讲的课 😄

就我的经验来讲,这种项目都是用 rails 来做 web,grape 挂载上去做 api,不存在同样的逻辑做 2 次,因为业务逻辑大部分都是做在 model 这一层,web 层和 api 的层的业务大多数情况不是完全一样的。而且 web 这一层验证一般用 cookie 做,而 api 这一层用 cookie 不是一个好的 practice,所有感觉还是分开比较好,也为你以后拆分成不同的模块做准备

#61 楼 @sufish 对的,哈哈,大神好。☺

学习了,菜鸟进来膜拜一下

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