引言
近来实践了下 Go Web 开发,也一并做了些 Web 开发相关的效率工具,目的也是为了提升开发体验,进而改善项目质量和提高开发效率。
之前在使用 Python 做 Web 项目时,比较习惯基于类的方式为某个资源提供 RESTful 接口。和采用纯函数的方式不同,基于类的方式可以使得资源管理更清晰,当逻辑代码较多时,代码结构也不至于太混乱。
所以在使用 Go 语言实践时,我们会采用类似 ResourceHandler
的结构体的方式来替代类,并给该结构体绑定相应的 GET/POST/DELETE/PUT/PATCH
方法,用以和 HTTP 相应的请求方法匹配。
遇到的问题
我们使用的 Web 框架是基于 go-chi/chi 封装的,这个框架本身的特点就是简单,与 Go 标准库 http
无缝衔接。主要用它来做路由分发,方便编写 HTTP API。
但在实践中,路由注册那块感觉很繁琐。比如,我们先前的写法大致是下面这样的:
1 | r := chi.NewRouter() |
显然,上面的写法有点啰嗦,且容易出错。比如在单元测试中,我们就可能会因为疏忽大意,给 test server
绑定的路由和实际的不同(别问我怎么知道的)。所以就希望能有一种简单的方式注册路由,并且让程序自己根据请求的 Method 分发到 Handler
对应的方法上。
既然遇到问题,那就得想个优雅的方法解决,尽可能保证性能损耗不大,但又能带来较好的开发体验。于是乎,rest 包诞生了。
说说 REST 包
rest 包的实现很简单,就是对 go-chi/chi 框架做了简单的包装,实现了一个自定义的 BaseHandler
实现请求分发到所谓的「子类」结构体对应的方法进行处理。由于并不会进行类型断言或者使用反射,所以带来的性能开销不会很高。
那么,在有了 rest 包后,我们如何使用它呢?
使用示例
假如我们要编写一个任务管理服务,需要提供的接口如下:
功能 | 方法 | URL 示例 |
---|---|---|
获取任务列表 | GET | /tasks |
新建任务 | POST | /tasks |
获取单个任务 | GET | /tasks/1 |
删除单个任务 | DELETE | /tasks/1 |
在 rest 的助攻下,我们可以这样来写:
1 | func main() { |
项目结构
这个项目结构仅供参考,也没啥特别的:
1 | . |
总结
怎么样,新的路由注册方式是不是更加简单了?其实这正是借鉴了 Python Tornado 的一些思想。我不认为这种做法是所谓的反模式,或者不符合 Go 的风格(欢迎来喷)。其实这压根和语言无关,为什么不愿意去实践一些能够改善开发体验的新方法呢?况且还能够减少犯错!