作者:Run,总架构师
工作经验 5 年,当前就职行业知名技术公司,担任总架构师职位,擅长前后端技术栈,Ruby、Python、JavaScript 等语言。
内容目录
针对 Grape 框架的改进
深层 expose
你也许知道,expose
可以嵌套。如果传递的是个不带参的块,则会执行嵌套渲染:
class Entities::Article < Grape::Entity
expose :user do
expose :name { |article| article.user.name }
expose :age { |article| article.user.age }
end
end
如上,会渲染一个如下的数据结构:
{
"user": {
"name": "Jim",
"age": 18
}
}
如果此时传入 deep: true
,则嵌套层绑定的 instance 就不一样了。下面的例子与上面的例子展示了同样的效果:
class Entities::Article < Grape::Entity
expose :user, deep: true do
expose :name
expose :age
end
end
接口返回值声明
status
用于声明接口的返回值(success
、fail
、entity
是特殊情况下的别名)。它有如下几种基本的调用形式:
status 200 do
expose :article, using: Entities::Article
end
status 200, '返回文章数据' do
expose :article, using: Entities::Article
end
status 400, '请求参数有误' do
expose :code, desc: '错误码'
expose :message, desc: '错误消息'
end
success '请求成功' do
expose :article, using: Entities::Article
end
fail '请求失败' do
expose :code, desc: '错误码'
expose :message, desc: '错误消息'
end
entity do
expose :article, using: Entities::Article
end
上述声明主要起两个作用:
- 在接口逻辑中调用
present
方法不用显示地指定Entity
类型,它是自动解析的。以前,你必须这样调用:
present :article, article, with: Entities::Article
现在,只用这样:
present :article, article
因为在
status
声明中它已经知道如何渲染article
实体了。 status
声明可对应生成文档。
参数、返回值一体化
Grape::Entity
新加一个 to_params
方法,使得你可以在参数声明中重复利用:
params do
requires :article, type: Hash do
optional :all, using: Entities::Article.to_params
end
end
它比 Grape::Entity.documentation
更好用,做了如下改进:
type
可以写成字符串:expose :foo, documentation: { type: 'string' }
可混用额外参数,如同时指定
param_type
参数:expose :foo, documentation: { param_type: 'body' }
合理处理了
is_array
:expose :bar, documentation: { type: String, is_array: true }
针对测试的改进
现在可以用编程 style 的方式测试接口的返回值了,不需要再测试诸如 JSON 字符串、XML 文本之类的。如果像下面这样实现接口:
present :article, article
就可以像下面这样测试:
get '/article/1'
assert_equal 200, last_response.status
assert_equal articles(:one), presents(:article)
注意,这个功能不是实现在框架中的,需要克隆脚手架项目:
git clone https://github.com/run27017/grape-app-demo.git
新手如何上手
如果你是完全的新手,建议先从熟悉 Grape 框架开始。我建议您阅读我仓库下的文档。在您熟悉了 Grape 框架以后,再阅读以上我对 Grape 框架改进的部分。关于整个框架的设计理念,可阅读后文。
项目上手就从我提供的脚手架项目开始,所有功能都集成进来了:
git clone https://github.com/run27017/grape-app-demo.git
理念
面向文档的开发
当今环境下,有许多的开发范式供后端开发者选择,例如_测试驱动开发_、_行为驱动开发_、_敏捷软件开发_等等。与之相对的,我提出了一个新的想法,我将其称为_面向文档的开发_。
写 API 项目的同时是要准备文档的。我不知道大家是如何准备文档的,但往往都逃不出一个怪圈:同一个接口,我的实现要写一份,我的文档也要同时写一份。我常常在想,为什么我在写接口的同时不能将文档同步地完成呢?换个角度想,接口文档的契约精神为何不能自动地成为实现的一部分?如果,我能发明一个 DSL,在编写文档的同时就能够制约接口的行为,那不正是我想要的吗?
说干就干!
我发现 Grape 框架就已经提供了类似的 DSL 了。例如你在制定参数时可以像这样:
params do
requires :user, type: Hash do
requires :name, type: String, desc: '用户的姓名'
requires :age, type: Integer, desc: '用户的年龄'
end
end
上面的代码就可以对参数进行制约,限制参数为 name
和 age
两个字段,并分别限制它们的类型为 String
和 Integer
. 与此同时,一个名为 grape-swagger 的库可以将 params
的宏定义渲染成 Swagger 文档的一部分。完美,文档和实现在这里结合了。
另外,Grape 框架提供了 desc
宏,它是一个纯文档的声明供第三方库读取,不会对接口行为产生任何影响。
desc '创建新用户' do
tags 'users'
entity Entities::User
end
但是,毕竟 Grape 框架不是完全的面向文档的开发框架,它有很多重要的使命,所以它和文档的无缝衔接也就仅限于此了。你能看到,params
宏是个完美结合的范例,desc
宏很可惜只与文档渲染有关,然后就别无其他了。
鉴于 Grape 框架是个开源框架,修改它以添加几个小零件还是很简易的一件事。我用了几天的时间添加了一个 status
宏,可以用它来声明返回值:
status 200 do
expose :user, deep: true do
expose :id, documentation: { type: Integer, desc: '用户的 id' }
expose :name, documentation: { type: String, desc: '用户的姓名' }
expose :age, documentation: { type: Integer, desc: '用户的年龄' }
end
end
上述声明主要起两个作用:
- 在接口逻辑中调用
present
方法不用显示地指定Entity
类型,它是自动解析的。以前,你必须这样调用:
present :user, user, with: Entities::User
现在,只用这样:
present :user, user
因为在
status
声明中它已经知道如何渲染user
实体了。 grape-swagger
库可以解析status
宏生成文档。
一切还只是冰山一角。
你真的不需要 Controller 测试吗?
有关接口的单元测试,有两个观点争论不休:接口测试应该是针对 Integration 测试还是 Controller 测试?Integration 测试像是一个黑匣子,开发者调用接口,然后针对接口返回的视图进行测试。而 Controller 测试也会一样地调用接口,但会测到内部的状态。
通过下面的两个案例直观地感受一下 Controller 测试和 Integration 测试在 Rails 框架中的不同写法。
在早期的 Rails 版本中,是有 Controller 测试的:
class ArticlesControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
assert_equal users(:one, :two, :three), assigns(:articles)
end
end
Rails 5 以后,更推荐 Integration 测试:
class ArticlesControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get articles_url
assert_response :success
end
end
注意到 Integration 测试中是没有对应的 assert_equal
语句的,因为它比较难写。如果视图返回的是 JSON 数据,可以尝试用下面的等效写法:
assert_equal users(:one, :two, :three).as_json, JSON.parse(last_response.body)
但是测试视图的代码及其依赖视图的变化,也会经常性的失效,上面只是尝试性的一种写法而已。
我不想在这里探讨 Controller 测试和 Integration 测试究竟孰优孰劣的问题,尽管你可能已经从字里行间察觉到我的倾向性。关于这个话题能够从 2012 年讨论到 2020 年,不信你可以看这个帖子。我可不想让情况变得更糟。很多次我在想,那些反对 Controller 测试的人可能仅仅把 Controller 的实例变量看成是其内部状态而已,而没有意识到它也是 Controller 与 Template 之间的契约。这不怪他们,因为用传递实例变量的方式定义契约确实不够优雅。
好在我做了一些简单的工作,让其在 Grape 框架内可以更优雅地测试接口的返回。你只需要在逻辑接口中使用 present
方法指定渲染数据:
present :users, users
然后就可以在测试用例中结合特殊的 presents
方法测试它:
assert_equal users(:one, :two, :three), presents(:users)
跟 assigns
很像,但是它更舒服不是么?
写在最后
这就是我对 Grape 框架的改造过程,已经开始并将持续下去。我的改造理念无外乎两个想法:更好的文档结合和更好的测试。而事实上,只需要一点点工作,确实就可以起作用了。
如果你也想用到我所改造的 Grape 框架,直接克隆我的脚手架就好了:
git clone https://github.com/run27017/g...
脚手架中使用的是我 Fork 后的框架集,它们是:
点击它们的链接看一看吧,也许你也能成为开源世界的贡献者呢。
推荐
如果想了解学习更多可以看我最新录制的课程
课程链接:https://ke.sifou.com/course/1...
设计这门课的初衷
- 当前 Web 开发的主流模式:前后端分离
- Web API 开发,没有必要五花八门,应采用最佳实践
- 将自己多年的实践经验总结并分享
这门课在讲什么?
- 遵循标准:最小惊讶原则
- 接口的完备性:输入和输出、鉴权、错误处理等
- 与框架整合
为什么每个人都应该有一套自己的脚手架?
- 框架只是通用模版的提供者,并没有安排具体的解决方案
- 框架并没有细化到最佳实践
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。