Go Gin框架是怎么设计出来的?
说起go里面的gin框架,做过web开发的人可能是无人不知,无人不晓了,但是它诞生的故事却很少有人知道,下面就来了了gin的故事。以gin作者第一人称的方式打开。
那是2014年的事,我刚从旧金山飞回西班牙,脑子里全是没做完的梦。我的经理——也是我当时的创业合伙人——看完我写的几行代码,摘下眼镜擦了擦,用一种"我见过世面,但你小子有点东西"的语气说:
“这个Gin,简单得有点过分啊。”
我愣了一秒,然后笑了。因为我知道,这大概是我这辈子收到的最好的夸奖。
故事的开始:一个失败的产品,和一个成功的工具
那年我20岁出头,刚在旧金山待了一年,为两个小团队做SDK,然后飞回西班牙准备念"电信工程"。手痒,想做个产品,于是写了个叫Fyve的社交网络——基于兴趣的那种。后端我选了Go,因为那时候Go刚有点名气,我用起来感觉"这语言靠谱,不整花活"。
为了写Fyve,我需要一个Web框架。当时Go社区最火的是Martini。它的README写得漂亮,中间件模型优雅,你随手就能起一个路由跑起来。但问题是——Martini用反射做依赖注入。这意味着:
- 它很"聪明"——你的handler里凭空就能拿到一个服务对象,不需要任何"接线"代码。
- 它也很"吓人"——当出了bug,你得追着一堆隐式依赖去找问题出在哪。
- 更关键的是,所有这些反射,都在每次请求时发生。
我那时候刚看过Rob Pike的演讲《Simplicity is Complicated》,里面有一句话像钉子一样钉在我脑子里:
"简单"往往需要构建者付出更多努力,但使用者需要付出的努力更少。
而"容易"正好相反——看起来很美,但长久来看,你欠下的债会慢慢找上门。
Martini就是"容易"的典范。而我想写一个"简单"的东西。
简单,不容易
Gin的设计目标很简单:站在net/http和 Martini 的正中间。
net/http给足了你控制权,但几乎不帮你任何忙。你自己写路由解析、参数提取、响应渲染,每个handler都在重复同样的"管道工"活儿。不难,但烦。- Martini给了你太多"魔术",你只需要写业务逻辑,剩下的它帮你"变"出来。但当你需要debug时,你完全不知道这个"魔术"是怎么变的。
Gin的做法很"土",但很有效:
- 没有反射,所有类型在编译期就确定。
- 只有一个核心对象
Context,它带着请求、响应、路径参数、验证助手、渲染方法——所有你需要的东西都挂在它身上,你只需要把它传下去就行了。
r:=gin.Default()r.GET("/users/:id",func(c*gin.Context){id:=c.Param("id")// 路径参数,无反射c.JSON(200,gin.H{"id":id})// 响应渲染,一行搞定})r.Run(":8080")这个Context的设计还有一个有趣的"巧合":它在2014年就存在了,而Go标准库的context.Context是2016年才有的。后来标准库出了,我们保留了gin.Context的名字,但让它实现了标准库的接口。于是所有已有的代码继续工作,新代码还能把gin.Context当标准context.Context用。
这种"向后兼容的直觉",来自我做SDK那一年学到的教训:当你设计一个API时,你得假设十年后有人靠它吃饭。你不能随随便便就改掉它。
路由:一棵"正经"的树
Gin的路由器也是"简单"的体现。Martini匹配路由的方式是遍历一个正则表达式列表——灵活是灵活,但代价是每个请求都要做一堆正则匹配,而且路由越复杂,越慢。
Gin的路径语法很小:静态路径、命名参数、通配符。就这三样。而正是这个"限制"让Gin可以用基数树(radix tree)来做路由匹配——这也是httprouter在Go社区流行的原因。
用树的好处是:
- 匹配速度只和URL长度有关,和你注册了多少条路由无关。
- 共享前缀的路由共享节点,所以即使你有成千上万条路由,内存占用也很紧凑。
- 路径参数预分配,没有额外的内存分配压力。
性能好,不是因为Gin做了什么特别"聪明"的事,而是因为它做更少的事。而"做更少的事",也让读代码的人更容易理解。
零破坏性变更,十年承诺
Gin最让我骄傲的,其实不是性能,而是兼容性。
我从一开始就给自己定了个规矩:Gin的公开API,一旦发布,绝不破坏性地改动。
这听起来很"硬核",也很Go,但其实是"怂"——因为我太清楚,随便改API会让多少人骂娘。所以我养成了一个习惯:每次加一个新功能前,都问自己一句:
“这个API,我愿意维护十年吗?”
这个规矩一直守住了。十年前最早那批用Gin写的程序,到今天依然能编译、能跑。这比任何benchmark数字都让我有成就感。
后来:它"毕业"了
Fyve早就没了。Gin反而活了下来。
几年后,我把项目交给了后来的gin开发者Bo-Yi Wu和Javier Provecho,他们把它接过去,继续打磨,比我做得更好。我一直觉得,一个开源项目的最高荣誉,不是它有多少star,而是它不再需要原作者的照顾。
Bo-Yi Wu是一位go的全栈开发者,如果说 Gin 是我在 2014 年种下的一棵树,那 Bo-Yi Wu 就是那个长期负责浇水、修剪和防治病虫害的园丁。
到今天,Gin大概有90k star,29万+项目依赖它。Star是虚荣指标,我一般不当回事。但依赖数——那是有人押注你的API不会背叛他们,而这个赌注,赢了十几年。
最后,给我经理一个回答
如果今天我的经理再看到Gin,问我"这框架怎么简单得这么过分"——
我会说:因为它从一开始,就只想做好一件事,而且愿意为"简单"付出比"容易"更多的代价。
简单,不等于容易。
但简单,会活得比容易更久。