问题是这样的,准备开发的应用会涉及 WEB 端和 APP 端,所以,打算用 Grape 做 api,rails 做 web 页面
但是这样,是不是意味着同样的业务逻辑需要在 Grape 和 Rails 里分别写一遍?或者干脆 WEB 端也前后分离,直接调用 API?
迷茫,有什么好的实践经验可以分享吗?
如果你又要 api 又要 html view,我觉得就不要用 grape 了吧。。直接用 rails 的 json builder 吧= =controller 看上去会舒服很多。
@hewe active_record 是充血模型 逻辑都放在对应的领域模型里 只有在过于复杂跨多个领域模型的时候才会写 service 但是逻辑都不会写在 controller 里面
最近刚好用 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/ 这些人的例子。
#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 只看过简单 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
#21 楼 @numbcoder Web 项目,浏览器输入的 param 一样需要严格校验的,否则被恶意请求搞出各种问题。
互联网应用的准则之一,就是不要相信任何外部的输入。
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 注入。
#17 楼 @numbcoder 额 我好像没 get 到你这的点呢。
校验是放在 model 里的,当然你要在 action 里做一次校验,没什么不好的。如果你是怕多查数据库的话,你可以加个 redis 限制 api 的访问就好了
我们的 rails 项目就是纯 api 的,效果还行
#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
), 后期随时扩展,默认是 closedundefined
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
借助 Swagger, 上面那段代码可以转化为前端友好的文档。live demo: http://petstore.swagger.io/
裸写,具体位置可以放在 controller, 也可以放在 model 层。当然作为一个 Rails 用得不够熟练的码弱,还期待 LS 的指教。
Let's ping pong~
接 #34 楼 @42thcoder 的例子,我再来举一个 Get 请求,不验证参数导致的安全性问题
我曾经见过不少新手都是这么写的
API 的原本意图是是可以按 created_at
或 updated_at
排序来取数据
# PostsController
def index
@posts = Post.order("#{params[:sort]} #{params[:order]}")
end
如果 API 的调用者,故意给 sort
参数传了一个未加索引的字段,或者是一个 text
类型的字段,可以看看你们的数据库还能撑多久
#35 楼 @42thcoder @numbcoder 说来惭愧,我们的项目在 get 的时候大多数情况没在查询之前做参数验证,觉得我们开放出来的查询都会有索引,所以传错值问题引起的性能损失不大。主要考虑的还是安全问题,比如不做校验,会带来哪些安全上的风险呢?
如果没有 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 行代码可以搞定(包括查询)
#44 楼 @numbcoder 换个角度思考,如果 rails 配合一个 controller 层面的参数校验 gem,比如 https://github.com/nicolasblanco/rails_param ,是不是就和 grape 一样很方便做 api 了?或者根据项目需求,类似我举的 ransack 扩展,是不是就比 grape 更灵活?
其实归结到一句正确的废话,对于 grape vs rails api,或者 webpack vs asset pipeline 等类似问题,答案都是你熟悉哪个用哪个,哈哈哈哈
#43 楼 @quakewang 赞你的说法,不知道楼主在喷什么,也不能因为前面有参数验证就这么写,order("#{params[:sort]} #{params[:order]}") 这种不能容忍
#48 楼 @numbcoder 我说的是尽量避免拼接 sql, 你这边 order 需求要那么多种情况的话,也只能这样。不过因为 order 的类型也就那么几个,用 include 是可以完全防住,其他的难道都能用 include 防住?最好还是这样 User.find_by(email: params[:email])
这个问题我一开始也纠结过。先分享下我现在的用法和项目背景 因为是纯 API 服务,所以
对于你这个的问题。我的想法是这样的: 分析 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
@numbcoder 明白你的意思,单纯来讲只是觉得你给的例子很不靠谱,类似 email, CID, phone 这种验证完全可以通过 rails validation 来处理而且并不会产生查询,再者这种新手写的这种 query 做法 (stupid) 跟上面讨论的(选谁或者是否校验)优劣性是 2 个问题。
其实 Grape 那个 params 验证还是挺需要的,Ruby China 重新基于 Rails API 来改写了 API 以后,还是自己实现了一个类似的 params 验证机制(预言未来 Rails API 会自带这样的功能)
https://github.com/ruby-china/ruby-china/pull/612
然后再说 Grape 那种自动生成文档,其实也是看起来很美,还是太粗糙了,尤其是你没法写太多例子,不然代码里面全是一大堆一大堆的注释了,真正大型复杂的项目还是得用 https://apiary.io 这类工具。
看了这么多讨论貌大家都没有回复:是不是意味着同样的业务逻辑需要在 Grape 和 Rails 里分别写一遍?或者干脆 WEB 端也前后分离,直接调用 API?其实我也想知道答案,另外如果 web 和 app 项目只有 60% 业务逻辑可以公用又该如何选择呢?
就我的经验来讲,这种项目都是用 rails 来做 web,grape 挂载上去做 api,不存在同样的逻辑做 2 次,因为业务逻辑大部分都是做在 model 这一层,web 层和 api 的层的业务大多数情况不是完全一样的。而且 web 这一层验证一般用 cookie 做,而 api 这一层用 cookie 不是一个好的 practice,所有感觉还是分开比较好,也为你以后拆分成不同的模块做准备