由于工作需要,这些年来也接触了不少的开发框架, Golang 的开发框架比较多,不过基本都是Web”框架”为主。这里稍微打了个引号,因为大部分”框架”从设计和功能定位上来讲,充其量都只能算是一个组件,需要项目使用的话得自己四处再去找找其他的组件,或者自己造轮子。如果用于Web开发,这些”框架”的Web开发能力均已完备,无太大差别,且均是自标准库net/http.Server的二次封装。由于框架众多,这里笔者只选择了几个曾做过技术选型评估、较为熟悉,且目前比较流行和典型的Golang”框架”,从适用于业务项目开发框架的角度,做一个简单的横向比较,以便大家在项目框架选型时做个参考。

评估指标 横向比较
  • 以下部分对比参数涉及评分的部分,满分总共按照10分为标准。
  • 如果标记为”-“的部分,表示不支持或者需要引入第三方插件支持。
  • 以下特性如果官网提供文档则直接提供文档地址,找不到文档但是笔者知道有就会简单标注。
  • 各个”框架”功能特性实现不同,在文档、功能、易用性上存在较大差异,各位朋友可自行查阅链接。
经验分享

不同的需求场景,存在不同的选择。选择适合的工具,解决适合的问题。

开源不存在孰好孰坏之分,开源作者能够本着开源精神给大家分享技术成果用以学习和使用,这本身就是一件非常不易并且值得称道的事情。

最后,笔者在这里跟大家分享一下自己所在团队的情况,以及在 Golang 技术栈转型过程中所走的弯路,希望能在框架选型这一环节,能给大家作一定参考。

团队最初痛点

团队转型 Golang 技术栈的一些背景。主要几点:

  1. 团队后端最初的主要技术栈为 PHP ,由于业务发展需要,进行 微服务 改造。第一版微服务采用了 PHP + JsonRpc 的通信方式。
  2. 随着项目增多,公司也组件了自己的 DevOps 团队,底层部署转向了 Docker + Kubernetes 容器架构,并且引入了 Golang 技术栈。
  3. 由于一些痛点,通过一段时间对 PHP 和 Golang 的比较,团队决定快速转型 Golang 技术栈,主要痛点如下: PHP 项目在业务复杂后、项目中后期的开发和维护成本整体偏高。主要原因还是其过高的灵活性,非结构化的变量设计,参差不齐的开发人员素质。上云容器化部署后, PHP 的 DevOps 效率太低。复杂的 Composer 版本管理,超大的 Docker 镜像大小,都影响着 DevOps 的效率。相比较而言, Golang 效率极其高效。 Json Rpc 通信协议设计下,接口的扩展性和灵活性很高,但服务之间很难快速确定接口的输入与输出定义,只能根据文档和示例进行对接和维护。由于代码和文档分离,大部分场景下接口文档维护往往滞后于接口变化。随着服务的不断增加,非结构化的 通信协议 管理使得服务接口的开发和维护成本进一步提高。 JsonRpc 的通信协议本质基于 HTTP1.x + Json ,执行效率过低,算不上真正的微服务通信协议,很难对接上主流的服务治理框架。相比较基于 HTTP2.x 的 gRPC 协议有着成熟微服务开发框架和服务治理解决方案。业务梳理的考量, PHP 到 Golang 技术栈的迁移,其实也是一次技术重构的契机,在技术重构的过程中也重新梳理业务系统设计,偿还技术债务。
进一步的痛点

Golang 确实足够简单,相比较其他的解释类开发语言,没有过多的语法糖和语言特性,因此团队上手很快,并快速完成了一部分业务系统的技术重构。但随之而来的是更加严重的痛点。主要几点:

  1. 轮子过多: Golang 实在太简单了,以至于我们的团队成员爆发了压抑许久的闷骚劲,充分发挥”造后不管”的造轮精神,开发/封装了许多大大小小的轮子。这些轮子均能满足最基本的功能,例如:日志、配置、缓存等等。但轮子并不是实现一个基础功能的半成品就了事,需要保证功能性、稳定性、扩展性和可维护性,要能结合更多生产实践验证,更需要能够长期维护、持续进行迭代改进。否则,就是一堆大小不一的成人玩具。造轮一时爽,维护火葬场。直到现在,我们还在为分散在 100 多个 Golang 项目中的数十个成人玩具做大统一的事情痛苦不已。当然,这个问题也跟组织架构和团队管理也有很大关系。
  2. 不成体系: 我们坚信一个 package 只做一件事情,并且特地使用 单仓库包 的形式进行包管理,相当于每个 package 都是独立维护的 git 仓库。其实 单仓库包 和 package 设计并不存在必要性,反而独立的单仓库包提高了组件和框架的维护成本。这种单仓库包设计难以形成技术体系,在团队技术管理上,难以形成统一的技术框架。单仓包显得很孤立,而一个技术体系的建立除了需要制定规范和标准,更需要技术框架来准确落地。一个成体系的、统一的技术框架,至少涉及到数十个基础技术组件,不可能完全孤立设计。每一个 package 的基础功能实现都很简单,但是如何能够统一组织在一起却不是一件简单的事情,这需要团队的技术管理者需要有一定的技术底蕴、格局和前瞻性,而不是和普通开发者那样眼界只能局限于 package 本身。这种孤立的单仓库包设计,对于业务项目的规范化约束不强,每一个组件都可以独立替换,也至于痛点1的问题越发严重(连日志组件都好几套,虽然都满足基本的日志规范设计)。最终,我们最初引以为傲的单仓库包设计,最终变成了一堆散沙。例如,就连需要增加标准化的链路跟踪功能,由于单仓库包过于散乱和不统一,使得推进改进成本极其高昂。除了使得技术体系难以建立,技术规范难以准确落地之外,每个组件的易用性也设计得较差。举个简单例子,我们的日志组件、缓存组件、数据库组件、HTTP/gRPC Server组件都需要对接配置管理功能,单仓包需要保证低耦合设计,因此开发者在使用的时候需要先手动读取配置、并转换为目标配置对象、并注入到对应的组件初始化方法中,随后才能将该对象运用到业务逻辑中,若干个业务项目均是重复此步骤。其实 goframe 在这块的易用性设计就挺不错,每个包当然是独立设计的,在统一的技术框架体系下,再独立提供一个 耦合 的单例模块将常用的对象进行单例化封装,自动实现配置读取、配置对象转换、配置对象注入及组件对象初始化,开发者仅需要调用一个单例方法即可。而这个常用单例模块,成为了我们技术框架体系的一部分,极大地提高了业务项目的开发和维护效率。
  3. 版本不一致: 在业务项目不断增多之后,轮子版本不一致性也越来越明显。什么是版本不一致?举个例子。我们有个轮子叫做 httpClient ,总共发布了 10 来个版本;我们总共有 100 多个 Golang 项目,几乎每个版本都在使用。我们提交了一个 bug fix ,却难以让所有项目都能更新。对其他的轮子也是类似的情况,况且我们也有数十个各种轮子,被各个项目独立使用中。

经过反思总结,总结了以下几点:

  1. 团队需要一个统一的技术框架,而不是东拼西凑的一堆单仓库包。
  2. 我们只需要维护一个框架的版本,而不是维护数十个单仓库包的版本。
  3. 框架的组件必须模块化、低耦合设计,保证内部组件也可以独立引用。
  4. 核心组件严格禁止单仓库包设计,并且必须由框架统一维护。
最终的抉择

走过这么多弯路之后,我们决心建立一套成体系的 Golang 开发框架。除了要求团队能够快速学习,维护成本低,并且我们最主要的诉求,是核心组件不能是半成品,框架必须是上过大规模生产验证的,稳定和成熟的。随着,我们重新对行业中流行的技术框架做了技术评估,包括上面说的那些框架。原本的初衷是想将内部的各个轮子统一做一个成体系的框架,在 开源项目 中找一些有价值的参考。

后来找到了 goframe ,仔细评估和学习了框架设计,发现框架设计思想和我们的经验总结如出一则!

这里不得不提一件尴尬的事情。其实最开始转 Golang 之前(2019年中旬)也做过一些调研,那时 goframe 版本还不高,并且我们负责评估的团队成员有一种先入为主的思想,看到模块和文档这么多,感觉应该挺复杂,性能应该不高,于是没怎么看就PASS。后来选择一些看起来简单的开源轮子自己做了些二次封装。

这次经过一段时间的仔细调研和源码学习,得出一个结论, goframe 框架的框架架构、模块化和工程化设计思想非常棒,执行效率很高,模块不仅丰富,而且质量之高,令人惊叹至极!相比较我们之前写的那些半成品轮子,简直就是小巫见大巫。 团队踩了一年多的坑,才发现团队确实需要一个统一的技术框架而不是一堆不成体系的轮子,其实人家早已给了一条明光大道,并且一直在前面默默努力。

经过团队内部的调研和讨论,我们决定使用 goframe 逐步重构我们的业务项目。由于 goframe 是模块化设计的,因此我们也可以对一些模块做必要的替换。重构过程比较顺利,基础技术框架的重构并不会对业务逻辑造成什么影响,反而通过 goframe 的工程化思想和很棒的开发工具链,在统一技术框架后,极大地提高了项目的开发和维护效率,使得团队可以专心于业务开发,部门也陆续有了更多的产出。目前我们已经有大部门业务项目专向了 goframe 。平台每日流量千万级别。

感谢原作者