关于基于接口定义的开发流
背景
之前做前端开发,需要依赖后端同学的接口文档,涉及到一些老接口的时候,经常出现接口信息疏于维护的情况,然后找这个找那个的,依然很难做到让接口信息完整,当时就在想,为什么不再要修改一个接口的时候,把接口文档顺便维护了呢?心中甚是鄙夷。
后来做后端开发,经常出现一些要求快速完成进行联调的情况,更甚至有临时改变一些功能的情况,自己可能顺手就加上了,然后上到测试环境让前端同学调一下,然后……就没有然后了,因为运行起来没什么问题,也没有谁让把接口文档补全,就这样搁置了。
再后来后端开发也要兼顾接口测试,接口测试 的用例梳理,是基于接口定义,一方面找到接口的参数边界,另一方面测试完整的场景,这时候才发现,之前的接口文档写得是多么简陋。
于是,就开始探索更好的后端接口开发工作流。
灵感来源及畅想
灵感1
接口文档的维护大体上分为两派,① 接口文档应当跟随代码,例如 swagger ② 接口文档管理在专门的接口平台,例如 yapi。 这两派都有其拥护者。目前看到的,市场上很多企业还是走的第 ② 条路,主要原因,可能还是对后端人员来说上手简单,有好用的 UI,写接口就点点点几下;再加上给前端同学交付比较方便(仍一个链接过去),带有 mock 功能,可能还有一些简单的 接口测试 功能,用起来很流畅。 用这种方式的一个弊端,可能就是 容易疏于维护
。
基于代码的方案基本就是 swagger 的方式,在定义接口时,用 注释
或者 装饰器
等方式,将接口的元信息嵌入到代码中,这样就可以做到修改代码的时候,就可以顺便修改接口信息。 但这样的弊端就是: ① 部署不方便,yapi 可以独立部署,很稳定,基于代码的则可能随意改变,对使用方而言不够稳定。 ② mock 、test 等功能较弱,有些时候需要提供 这些功能时比较容易被要求更换。
灵感2
之前系统还不是很复杂时,主要对外提供 http 接口,后来,服务越来越多,服务间的通信则主要使用 rpc 方式,我们选择的 grpc 框架,该框架在创建接口时的方式,是基于 proto3 定义的接口,使用 protoc 直接生成对应的代码,将接口定义变成真正的 接口
,然后由开发者自行实现该接口,达到提供真正的接口功能的目的。
我在想,http 和 rpc,都是远程通信的方式,从目的上看,本就没什么区别。看起来,只是 rpc 是基于自定义的 数据格式
及 序列化方式
,而 http 整体看是个通用的文本协议
。那么,是不是可以把 grpc 中的一些优点,应用到 http 的请求中来,甚至应用到 ws 等长链接协议中?
后来,看到 go-zero 这个项目中,脚手架工具的 go-ctl 就已经包含了这项功能,基于一个自定义的接口定义文档,生成一定模板下的基本代码。觉得十分好用。
更多探索
在探索的过程中,观摩了几个 web 开发框架,可以算得上是典型了:
他们分别有自己的开发工具体系。
goframe
是采用 将接口信息写在代码中
的方式,通过解析 request 和 response 中的 meta,以及在注册路由时添加的一些额外信息,生成 openapi 格式的 json 文档,实际上,这也是生成 swagger 所需要的信息,然后直接在框架中继承了 swagger。 对于开发者而言,仅需要关注代码即可,在代码中通过 tag
或者 显式描述 的方式,即可保证接口文档与接口实现一致,改动成本较小,学习成本较低。
go-zero
是自己实现了一套 接口定义 的语法,整体类似于 golang 和 proto3 的结合体,词法语法的解析均是自己实现的,这也就意味着,go-zero 可以完全掌控 接口定义文档 到代码的生成过程,因此,其脚手架工具 go-ctl
提供了 api 和 rpc 两种接口代码自动生成的方式,开发者仅需要写 接口定义文档 即可生成基本代码,然后自己补充业务逻辑即可。另外,生成的基本代码是根据 模板 确定的,这也就在一定程度上支持了自定义生成代码的功能,值得更多探索。另外,生态中也有将 接口定义 和 swagger 格式对接 的工具了,可以生成 swagger 文档。
go-kratos
采用的是 grpc 的一套方案,grpc 的 语法解析 是 C++ 实现的,然后生成一套自定义 ast 的 FileDescriptor
结构体,这个结构体通过编码成二进制传递给其他程序;grpc 的代码生成是由各类 plugin 实现的,例如我们常用的 golang 的代码,则是由 protoc-gen-go
这个插件生成的(实际上有好几个常见的实现)。那么,也就意味着,只需要再实现一个将 proto3 的接口定义 转成一套 http 的代码的 plugin,也就可以实现和 grpc 一样的代码生成能力。 这也就是 go-kratos 采用的方案,插件的名字叫 protoc-gen-go-http
。 和 go-zero 类似,go-kratos 也提供了生成 swagger 的工具。
基于接口定义,我们可以做很多事情,例如: ① 基本代码自动生成(server、client) ② 接口文档自动生成 ③ 基本接口测试代码自动生成 ④ 协议转换 ⑤ mock 。有了这些自动化手段,做开发时就可以很纯粹地关心业务逻辑即可,这对于降低开发的复杂度而言,有着巨大的价值。
如何行动起来
探索这些的原因,在于我们当前的 ws 通信,接口层是自定义的 消息码
,前后端都需要维护一套必须一样的枚举值,并且前后端都要对每个消息码写一套大致相同的代码,十分繁琐。于是就想到,如果采用了和 grpc 一样的自动生成 服务端 和 客户端 代码的方式,那么 接口管理复杂度、代码复杂度、代码规范、健壮性、可调试性 等各方面都将得到很大的提升。
一切想法,要想产生真实的价值,就必须得要落地,而落地的方法,可以以实践为目标进行梳理,当有一定思路时,就赶紧行动起来。
目前,我可以先: ① 阅读 protobuff 的代码生产方式(c++写的,几乎完全没读懂) ② 阅读 go-ctl 的代码生成方式 (自实现的 ast 解析,代码比较规范,读懂大概) ③ 阅读 goframe 的接口文档生成方式 (自定义的结构以及转换,比较易懂)
然后: ④ 分析不同类型接口的差异 ⑤ 梳理 ws 生成的代码应该有些什么部分 ⑥ 以 ws 为例,开发一版基本代码
另外,补充一些其他维度的TODO:
- 重新学习一遍编译原理,弄清楚 词法解析 和 语法解析,以及将自定义语法变成 elf 格式的过程。
- 学习使用 正则 和 有限状态机 的方式进行词法、语法解析。
- 对 go-frame、go-zero、go-kratos、gin、go-kit 这几个框架进行更深层次的对比,从他们提供的功能 以及 功能的实现方式 等方面进行对比。
- 从开发效率的角度,探索各类工具对效率的提升程度,包括维护成本在内。可以先看看:
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!