美文网首页程序员
API的设计(3) - 实现方式

API的设计(3) - 实现方式

作者: 玩家翁伟 | 来源:发表于2018-09-26 15:33 被阅读5次

protoapi实现的方式

接上文,我们选择了protobuf作为IDL,而在代码生成的工具上,我是选择了使用go实现,因为:

  • go开发的程序可以方便的编译为windows / linux / osx上的可执行程序,并且几乎没有任何依赖,非常方便分发
  • go内置有模板引擎,便于代码生成
  • 相关资源丰富

protoapi的核心仅是protobuf的编译器protoc的插件,而protoc本身的运行命令类似:

protoc --api_out=lang=php:[output_path] [api.proto]

那么,这里是需要开发者分别下载protoc以及protoapi的可执行文件,设置好path路径之后才可以运行。

这对于熟悉protoc的开发者来说并不是问题,但对于不熟悉的童鞋来说,有可能产生困扰。

我们会认为,作为工具的提供者,应该尽可能的让使用者感到方便,故此,我们在实现protoapi时:

  • 选择了将protoc也嵌入其中
  • 使用者只需要下载protoapi,然后运行protoapi init命令
  • 便会自动下载所需的protoc,以及设置好path路径。

让使用者可以仅通过protoapi作为命令入口,做代码生成:

protoapi gen --lang=php --out=[output_path] [api.proto]

嵌套调用

运行protoapi gen命令的时候,protoapi实际上会:

  • 获得protoapi自身所在的路径
  • 生成protoc的命令参数,内部另开子进程调用protoc
  • protoc则又会再开子进程调用新的protoapi
    • 第二个protoapi进程,会检测自身的环境,区分自己是被protoc作为插件调用,又或者是被独立运行

嵌套调用类似:



protoapi进程,会检测自身的环境的代码类似:
// main.go
// when no any parameter and not reading from char device
// treat it as being called by protoc
if len(args) == 1 && err == nil && (stat.Mode()&os.ModeCharDevice) == 0 {
  // 被 protoc 作为插件调用
  // 执行代码生成的逻辑
} else {
  // 从命令行被调用,根据命令参数执行逻辑
    cmd.Execute()
}

语言模板

go本身内置有模板支持,text/template是在go 1.9的时候,甚至对生成的代码模板空行控制做了支持,处女座表示很爽。

这样我们可以让通过模板控制,输出空格、空行的排版都严谨的代码。

在开发调试的阶段,我们会希望使用独立的文件来编辑模板,然后实时生效;但在分发、使用的阶段,我们又会希望将这些模板文件跟主进程打包在一起,而不是分发多个文件。

在go里面,我们可以使用esc,自动的将静态资源打包至进程内,成为代码的一部分;但同时又可以通过环境变量,切换从硬盘读取源文件的模式。

debug_tpl属于protoapi一个undocumented的API。

命令行

cobra提供了一个命令行框架,可以方便的添加命令、设置参数、输出帮助等。

测试

开发过程中,经常可能会需要对模板、代码生成逻辑做修改、重构,需要有足够的测试用例支持,确保代码变动后,生成的代码不变。

在这里,自动测试代码实现起来也容易,只需要在程序开发至一定阶段后,拟定一个或者多个完成的proto文件,然后跑一遍代码生成,将生成的文件保存下来作为范本,以后有代码改动,就比较一下重新生成的代码跟范本是否有差异即可。

可以做成go test,也可以简单的搭配jenkins + 脚本做比较。

最后

protoc本身也有各种语言的代码生成,但它是针对直接使用protobuf做对象序列化的场景,而我们这里,首先是要兼容旧的web项目,使用json作为序列化,直接生成新的代码会便利一些。

但对于合适的场景,protoapi也会尽量采用protoc默认生成的代码,然后对其进行扩展。

下一篇我会继续讲protoapirestful / swagger的对比与关系。

相关文章

网友评论

    本文标题:API的设计(3) - 实现方式

    本文链接:https://www.haomeiwen.com/subject/szzdoftx.html