grape实践小结

grape
grape 是一个基于 Rack 的用来构建 RESTful API 的 Ruby 框架。它不仅提供了很好的 DSL 语法,而且还有很多配套的 projects。
grape 的 DSL 语法
我们可以用下面的代码来看看 grape 提供给我们的 DSL。
# /app/services/api/statuses.rb
class API::V1::Statuses < Grape::API
namespace :statuses do
desc 'Return a public timeline.'
get :public_timeline do
...
end
desc 'Return a status.'
params do
requires :id, type: Integer, desc: 'Status id.'
end
route_param :id do
get do
...
end
end
desc 'Create a status.'
params do
requires :status, type: String, desc: 'Your status.'
end
post do
...
end
desc 'Update a status.'
params do
requires :id, type: String, desc: 'Status ID.'
requires :status, type: String, desc: 'Your status.'
end
put ':id' do
...
end
desc 'Delete a status.'
params do
requires :id, type: String, desc: 'Status ID.'
end
delete ':id' do
...
end
end
end
namespace
指明了一个命名空间,它也有其它的别名:group
,resource
,resources
和segment
,我们可以根据情况使用desc
描述一个 APIparams
描述 API 的参数,grape 可以帮忙检查类型,而且可以在里面加入验证让 grape 帮我们处理route_param
代表一个路径参数,一般是 idget
post
put
delete
这些都是 RESTful API 的动作参数,代表 http request method.
version 制定
我们的代码在不断迭代,API 的版本也会不断迭代,grape 提供一个 version
的方法可以方便我们定义 API 的版本。
# /app/services/api/v1.rb
class API::V1 < Grape::API
# 在这里定义 v1 版本
version 'v1'
prefix :api
# 把 Statuses 这个 API 加载到 v1 下
mount Statuses
end
异常处理
grape 也提供 rescue_from
这样的方法来帮助我们处理异常,例如我们用 cancancan
来做认证的时候,可以用到这个。
# /app/services/api/v1.rb
class API::V1 < Grape::API
# 捕获 CanCan::AccessDenied 异常
rescue_from CanCan::AccessDenied do |_|
error! '401 Unauthorized', 401
end
# 捕获 ActiveRecord::RecordNotFound 异常
rescue_from ActiveRecord::RecordNotFound do |_|
rack_response('{"error": "资源不存在!"}', 404)
end
end
这样我们可以在外面统一处理这些异常
grape 的 before 和 helper
grape 的 helper 类似于 Rails 的 helper,也是在 API 里面定义一些帮助方法。
class API::V1 < Grape::API
# 引入 ::Helpers::BaseHelper 这个 module 里面的 helper 方法
helpers ::Helpers::BaseHelper
# 定义一些 helper 方法
helpers do
def authorize
....
end
end
end
这样我们就可以利用 before
来执行验证了。
class API::V1 < Grape::API
before do
authorize
end
end
这样每次 API 调用时,我们都会运行 authorize
来验证。
用 grape-swagger 来生成 grape 的文档
用 grape-swagger 可以帮助我们快速地建立 API 的文档(RAILS 项目可以用 grape-swagger-rails)。要用上 grape-swagger 需要在 API 里面增加 add_swagger_documentation
class API::V1 < Grape::API
add_swagger_documentation hide_documentation_path: true,
hide_format: true,
api_version: 'v1',
markdown:
GrapeSwagger::Markdown::RedcarpetAdapter.new(
render_options: { highlighter: :rouge }
)
end
后面那些参数是我们用到的一些设置,为了支持 markdown 语法,我们还需要增加 redcarpet(用于解析 markdown 语法) 和 rouge(用于语法高亮) 两个 gem
用 grape-entity 来包装 model
对于每个 API 来说,我们可能需要暴露的 model 的属性是不一样的,这时候我们就可以用 grape-entity 来做响应的格式化,控制暴露的属性,相当于 MVC 架构中的 View。
# /app/services/api/v1/entities/status_one.rb
module API::V1::Entities
class StatusOne < Grape::Entity
# 暴露 attribute_one
expose :attribute_one
end
end
# /app/services/api/v1/entities/status_two.rb
module API::V1::Entities
class StatusTwo < Grape::Entity
# 暴露 attribute_two
expose :attribute_two
end
end
# /app/services/api/statuses.rb
class API::V1::Statuses < Grape::API
namespace :statuses do
get :one do
# 使用 StatusOne entity
present Status.first, with: API::V1::Entities::StatusOne
end
get :two do
# 使用 StatusTwo entity
present Status.first, with: API::V1::Entities::StatusTwo
end
end
end
这样两个 API 返回的信息是不一样的,一个是 attribute_one,一个是 attribute_two。
使用 grape-attack 来做 API 的限流
我们用了 grape-attack 来做 API的限流保护,主要是 API 获取 token 这个功能用到。
# /app/services/api/statuses.rb
class API::V1::Statuses < Grape::API
namespace :statuses do
throttle max: 10, per: 1.minute
get :throttled_api do
...
end
end
end
这里我们限制了这个 API 一分钟最多被调用 10 次,更多的设置可以参考官网。
个人使用 grape 的一些体会
- grape 作为成熟的 API 框架,可以在参数里面负责很多的验证,可以很简单地生成文档,这都是我很喜欢的功能。
- API 的错误返回需要提前考虑,推荐返回是有不同的 http status(例如成功是 200, 参数错误是 400)跟 error code(在返回结果里面用到,这个由项目定义,表示发生的错误,例如
{ error_message: '数据请求错误', error_code: 4001 }
。 - 在用 grape-entity 的时候,我们会增加两个类
API::Entities::Model
和API::Entities:: ResourcesCollection
。
# 用于 singular 的情况
module API::Entities
class Model < Grape::Entity
expose :id
expose :created_at
end
end
# 用于 plural 的情况
module API::Entities
class ResourcesCollection < Grape::Entity
# 这里暴露 meta 属性返回分页信息,当然也可以增加其它内容
expose :meta do
expose :total
expose :per_page
expose :page
end
private
def total
return options[:total] if options[:total]
object[:entries].respond_to?(:total_count) ? object[:entries].total_count : object[:entries].count
end
def per_page
options[:per_page].present? ? options[:per_page] : Rails.application.config.per_page
end
def page
options[:page]
end
end
end
其它 Entity 可以分情况继承,方便暴露一些固定的属性。
- grape 可以结合 ransack 来集成搜索,排序的功能,主要是用 ransack 的条件规则作为参数规则,这样可以方便我们处理搜索跟排序的功能,这酸爽。