HTTP API golang 的选择

April 6, 2018

写一个 HTTP API 服务我们首先想到的就是找一个 Web framework 去实现。Framework 简单的来说就是设定一种代码运行的规范机制,所有业务逻辑相关的代码使用同一抽象的方式去运行。再提供一定的组合方式让代码的各个部件粘合起来,MVC 就是一种粘合方式。以前后端 HTML 模版的渲染做的就是 V(view) 这一层的东西,页面上用的数据 M(model) 这一层靠的是 C(controler) 在两者之间调度。不得不说 MVC 设计模式完美贴合了 web 开发,市面上所有号称 web framework 的东西都离不开 MVC 的身影,可见 Rails 在这方面开的先河还是非常优秀的。

现在微服务大行其道,前后端分离让后端可以专注于处理前后端数据交互,不用再关心前端的数据渲染, 打包,压缩,部署,测试,以及页面模版等一系列问题。以上要做的这些工作加上后端数据库相关的那一坨 sql 处理代码,以及各种缓存的处理让 Web framework 变得极其复杂。上面这些东西一般的语言实现起来都不会少于 10 万行的代码量。我们做一个项目写的业务逻辑超过十万行都是非常可观的代码量,维护起来不是那么容易的事情。

好马配好鞍,杀鸡别用牛刀。在前端技术爆发的年代,后端的 MVC 终于可以彻底把 View 层的重担卸载下来。当我们完全不用再考虑 View 层面里面的任何东西时,Model 层的重担我们也可以暂时放下,只需要关心 HTTP 的 request 和 response 即可。这样 Web 后端要处理的东西几乎没剩下多少,只需要找个地方可以装下 request 和 response 处理的地方,其他我们只需要再处理下 router 即可。在这种应用场景下,找不到任需要依赖大型 Framework 的理由。在额外的负担全部去掉的情况下,我们可以让后端的代码变得极其简洁,做到让任何人的能快速上手维护,尤其是使用 golang 可以不过多的依赖外部的 library。这里可以看我是如何实现的最小号的 HTTP API services。另外可以看我这篇文章是如何实现简单的 Model 层。

在项目里面我们平常会需要使用配置文件来设置一些参数让程序运行,比如连接参数, 生产环境与其他环境的不同运行参数等。常见的配置文件有 YML, Toml, xml, json, ini 等,这些配置文件本质上就是解析 key value 参数。与以往不同的是,在 golang 里面我们可以植入 Lua 来作为配置文件。Lua 这门语言本身就是植入式到宿主语言用的,它的虚拟机很小,只有 2K。在上世纪 8 ~ 90 年代时那时 C/C++ 这些系统级的静态编程语言大行其道。我们看到的很多古老的项目如 Vim, Emacs 它内部都附带了一个脚本语言作为插件拓展程序的功能,并且配置文件也是使用这个脚本语言。我甚至有时在想,如果那时候 Lua 流行起来,像 javascript 或 vimscript 这样的脚本语言真没有发明的必要。作为专门的植入式语言 Lua 比他们优秀太多了。

当我们植入 Lua 脚本到 golang 项目时,我们首先可以拿它来做配置文件。可以让项目的配置能像 vimscript 那样灵活,在配置文件里面直接实现逻辑判断,如下所示:

if system == "linx" then
    database = "postgresql"
else
    database = "sqlite"
end

一般的配置文件也可以实现以上功能,但是如果你打开你的 .vimrc 看看那些配置,我相没有任何配置文件能做到 .vimrc 那样的灵活程度。但只要你植入 Lua, 这些灵活的配置都能实现,这也是 lisp 这样的语言倡导的 “代码即数据” 的理念。golang 里面 Lua 的虚拟机有很多实现,我推荐使用 gopher-lua 项目,相对其它实现来说它的文档是最全的一个。

Lua 加入到项目中只用做配置文件也太浪费 Lua 强大的功能了。它最强大的地方就是可以很方便的与宿主语言交互去直接调用宿主语言暴露出来的一些接口,使用外部脚本拓展静态语言没法实现的动态功能。我们可以用它模拟 HTTP 请求,做像真实用户调用那样的测试,彻底抛弃像 insomnia 这种完全需要靠人肉去点击的 HTTP Client 测试工具。实现这个需求很简单,我们只需要暴露一些 http request 相关的接口到 Lua 虚拟机,然后运行一个测试用的 HTTP Server,再把 Lua 脚本加载进 Lua 虚拟机并执行,我们可以在代码里自由的控制脚本的运行和一些真实数据的模拟。详细的代码可以看我这里的实现