由于工作需要,这些年来也接触了不少的开发框架,Golang的开发框架比较多,不过基本都是Web”框架”为主。这里稍微打了个引号,因为大部分”框架”从设计和功能定位上来讲,充其量都只能算是一个组件,需要项目使用的话得自己四处再去找找其他的组件,或者自己造轮子。如果用于Web开发,这些”框架”的Web开发能力均已完备,无太大差别,且均是自标准库net/http.Server的二次封装。由于框架众多,这里笔者只选择了几个曾做过技术选型评估、较为熟悉,且目前比较流行和典型的Golang”框架”,从适用于业务项目开发框架的角度,做一个简单的横向比较,以便大家在项目框架选型时做个参考。
评估指标
指标
|
说明
|
指标
|
说明
|
基本介绍 |
来源各自官网。 |
模块化设计 |
是否支持模块化插拔设计、模块之间低耦合设计,是否可以独立使用其中某部分组件。 |
模块完善度 |
框架提供的功能模块是否丰富。模块能否能覆盖日常普遍的开发需求。 |
使用易用性 |
易用性不仅仅是值框架好不好用,更多是团队能否在低成本下快速接入,长期来看能否低成本维护。 |
文档完善性 |
参考官网提供的介绍资料,包括但不限于:文档、视频、示例、案例资料。同时,本地中文文档支持也是参考项。 |
工程化完备 |
是否能够快速接入项目开发,是否提供项目接入规范、设计模式、开发工具链,文档是否完善、源码是否易读、是否便于长期维护。 |
开发模式 |
框架适用的开发模式,或者官方推荐的开发模式。 |
工程规范 |
项目接入时的开发规范,如目录规范、设计规范、编码规范、命名规范等。 |
社区活跃 |
官方与社区沟通是否便捷,问题是否能够快速解答,BUG是否能够快速响应处理。 |
开发工具链 |
项目开发时使用到的CLI开发工具,如初始化项目、交叉编译、代码生成、swagger、热编译能力等等。 |
Web: 性能测试 |
来源第三方评测 https://github.com/the-benchmarker/web-frameworks 。 |
Web: 路由冲突处理 |
存在路由注册冲突时有无良好的解决方案,在业务项目开发中比较常见。 |
Web: 域名支持 |
Web路由是否支持域名绑定,甚至多域名的绑定。 |
Web: 文件服务 |
Web服务是否提供静态资源的访问能力。 |
Web: 优雅重启/关闭 |
Web服务在重启时不会影响请求执行,关闭时会等待正在执行的请求处理完,新请求不再接入。 |
ORM |
框架是否自带ORM组件,ORM组件是业务项目的核心组件。无论是自研还是通过第三方组件引入。 |
Session |
框架是否提供会话管理组件,无论是通用型Session组件,还是仅针对于Web服务的Session组件。 |
I18N支持 |
国际化组件支持(常用但非核心组件)。 |
配置管理 |
配置管理也是框架需要完备的核心组件能力。 |
日志组件 |
日志组件也是框架需要完备的核心组件能力。 |
数据校验 |
数据校验也是框架需要完备的核心组件能力。 |
缓存管理 |
缓存管理也是框架需要完备的核心组件能力。无论是内存还是Redis,无论是自研还是通过第三方组件引入。 |
资源打包 |
支持将依赖的文件资源例如静态资源、配置文件等固定文件编译到可执行文件中。框架组件自动支持资源检索。 |
链路跟踪 |
框架是否具备分布式链路跟踪能力,分布式跟踪在微服务架构中是必不可少的能力。 |
测试框架 |
框架是否支持单元测试接入,提供单元测试接入规范。无论是使用标准库还是第三方测试框架。 |
突出优点 |
比较明显的几点优点。 |
突出缺点 |
比较明显的几点缺点。 |
横向比较
- 以下部分对比参数涉及评分的部分,满分总共按照10分为标准。
- 如果标记为”-“的部分,表示不支持或者需要引入第三方插件支持。
- 以下特性如果官网提供文档则直接提供文档地址,找不到文档但是笔者知道有就会简单标注。
- 各个”框架”功能特性实现不同,在文档、功能、易用性上存在较大差异,各位朋友可自行查阅链接。
经验分享
不同的需求场景,存在不同的选择。选择适合的工具,解决适合的问题。
开源不存在孰好孰坏之分,开源作者能够本着开源精神给大家分享技术成果用以学习和使用,这本身就是一件非常不易并且值得称道的事情。
最后,笔者在这里跟大家分享一下自己所在团队的情况,以及在Golang
技术栈转型过程中所走的弯路,希望能在框架选型这一环节,能给大家作一定参考。
团队最初痛点
团队转型Golang
技术栈的一些背景。主要几点:
- 团队后端最初的主要技术栈为
PHP
,由于业务发展需要,进行微服务改造。第一版微服务采用了PHP
+JsonRpc
的通信方式。
- 随着项目增多,公司也组件了自己的
DevOps
团队,底层部署转向了Docker
+Kubernetes
容器架构,并且引入了Golang
技术栈。
- 由于一些痛点,通过一段时间对
PHP
和Golang
的比较,团队决定快速转型Golang
技术栈,主要痛点如下:
PHP
项目在业务复杂后、项目中后期的开发和维护成本整体偏高。主要原因还是其过高的灵活性,非结构化的变量设计,参差不齐的开发人员素质。
- 上云容器化部署后,
PHP
的DevOps
效率太低。复杂的Composer
版本管理,超大的Docker
镜像大小,都影响着DevOps
的效率。相比较而言,Golang
效率极其高效。
JsonRpc
通信协议设计下,接口的扩展性和灵活性很高,但服务之间很难快速确定接口的输入与输出定义,只能根据文档和示例进行对接和维护。由于代码和文档分离,大部分场景下接口文档维护往往滞后于接口变化。随着服务的不断增加,非结构化的通信协议管理使得服务接口的开发和维护成本进一步提高。
JsonRpc
的通信协议本质基于HTTP1.x
+Json
,执行效率过低,算不上真正的微服务通信协议,很难对接上主流的服务治理框架。相比较基于HTTP2.x
的gRPC
协议有着成熟微服务开发框架和服务治理解决方案。
- 业务梳理的考量,
PHP
到Golang
技术栈的迁移,其实也是一次技术重构的契机,在技术重构的过程中也重新梳理业务系统设计,偿还技术债务。
进一步的痛点
Golang
确实足够简单,相比较其他的解释类开发语言,没有过多的语法糖和语言特性,因此团队上手很快,并快速完成了一部分业务系统的技术重构。但随之而来的是更加严重的痛点。主要几点:
- 轮子过多:
Golang
实在太简单了,以至于我们的团队成员爆发了压抑许久的闷骚劲,充分发挥”造后不管”的造轮精神,开发/封装了许多大大小小的轮子。这些轮子均能满足最基本的功能,例如:日志、配置、缓存等等。但轮子并不是实现一个基础功能的半成品就了事,需要保证功能性、稳定性、扩展性和可维护性,要能结合更多生产实践验证,更需要能够长期维护、持续进行迭代改进。否则,就是一堆大小不一的成人玩具。造轮一时爽,维护火葬场。直到现在,我们还在为分散在100
多个Golang
项目中的数十个成人玩具做大统一的事情痛苦不已。当然,这个问题也跟组织架构和团队管理也有很大关系。
- 不成体系:
- 我们坚信一个
package
只做一件事情,并且特地使用单仓库包的形式进行包管理,相当于每个package
都是独立维护的git
仓库。其实单仓库包和package
设计并不存在必要性,反而独立的单仓库包提高了组件和框架的维护成本。
- 这种单仓库包设计难以形成技术体系,在团队技术管理上,难以形成统一的技术框架。单仓包显得很孤立,而一个技术体系的建立除了需要制定规范和标准,更需要技术框架来准确落地。一个成体系的、统一的技术框架,至少涉及到数十个基础技术组件,不可能完全孤立设计。每一个
package
的基础功能实现都很简单,但是如何能够统一组织在一起却不是一件简单的事情,这需要团队的技术管理者需要有一定的技术底蕴、格局和前瞻性,而不是和普通开发者那样眼界只能局限于package
本身。
- 这种孤立的单仓库包设计,对于业务项目的规范化约束不强,每一个组件都可以独立替换,也至于痛点1的问题越发严重(连日志组件都好几套,虽然都满足基本的日志规范设计)。最终,我们最初引以为傲的单仓库包设计,最终变成了一堆散沙。例如,就连需要增加标准化的链路跟踪功能,由于单仓库包过于散乱和不统一,使得推进改进成本极其高昂。
- 除了使得技术体系难以建立,技术规范难以准确落地之外,每个组件的易用性也设计得较差。举个简单例子,我们的日志组件、缓存组件、数据库组件、HTTP/gRPC Server组件都需要对接配置管理功能,单仓包需要保证低耦合设计,因此开发者在使用的时候需要先手动读取配置、并转换为目标配置对象、并注入到对应的组件初始化方法中,随后才能将该对象运用到业务逻辑中,若干个业务项目均是重复此步骤。其实
goframe
在这块的易用性设计就挺不错,每个包当然是独立设计的,在统一的技术框架体系下,再独立提供一个耦合的单例模块将常用的对象进行单例化封装,自动实现配置读取、配置对象转换、配置对象注入及组件对象初始化,开发者仅需要调用一个单例方法即可。而这个常用单例模块,成为了我们技术框架体系的一部分,极大地提高了业务项目的开发和维护效率。
- 版本不一致:在业务项目不断增多之后,轮子版本不一致性也越来越明显。什么是版本不一致?举个例子。我们有个轮子叫做
httpClient
,总共发布了10
来个版本;我们总共有100
多个Golang
项目,几乎每个版本都在使用。我们提交了一个bug fix
,却难以让所有项目都能更新。对其他的轮子也是类似的情况,况且我们也有数十个各种轮子,被各个项目独立使用中。
经过反思总结,总结了以下几点:
- 团队需要一个统一的技术框架,而不是东拼西凑的一堆单仓库包。
- 我们只需要维护一个框架的版本,而不是维护数十个单仓库包的版本。
- 框架的组件必须模块化、低耦合设计,保证内部组件也可以独立引用。
- 核心组件严格禁止单仓库包设计,并且必须由框架统一维护。
最终的抉择
走过这么多弯路之后,我们决心建立一套成体系的Golang
开发框架。除了要求团队能够快速学习,维护成本低,并且我们最主要的诉求,是核心组件不能是半成品,框架必须是上过大规模生产验证的,稳定和成熟的。随着,我们重新对行业中流行的技术框架做了技术评估,包括上面说的那些框架。原本的初衷是想将内部的各个轮子统一做一个成体系的框架,在开源项目中找一些有价值的参考。
后来找到了goframe
,仔细评估和学习了框架设计,发现框架设计思想和我们的经验总结如出一则!
这里不得不提一件尴尬的事情。其实最开始转Golang
之前(2019年中旬)也做过一些调研,那时goframe
版本还不高,并且我们负责评估的团队成员有一种先入为主的思想,看到模块和文档这么多,感觉应该挺复杂,性能应该不高,于是没怎么看就PASS。后来选择一些看起来简单的开源轮子自己做了些二次封装。
这次经过一段时间的仔细调研和源码学习,得出一个结论,goframe
框架的框架架构、模块化和工程化设计思想非常棒,执行效率很高,模块不仅丰富,而且质量之高,令人惊叹至极!相比较我们之前写的那些半成品轮子,简直就是小巫见大巫。团队踩了一年多的坑,才发现团队确实需要一个统一的技术框架而不是一堆不成体系的轮子,其实人家早已给了一条明光大道,并且一直在前面默默努力。
经过团队内部的调研和讨论,我们决定使用goframe
逐步重构我们的业务项目。由于goframe
是模块化设计的,因此我们也可以对一些模块做必要的替换。重构过程比较顺利,基础技术框架的重构并不会对业务逻辑造成什么影响,反而通过goframe
的工程化思想和很棒的开发工具链,在统一技术框架后,极大地提高了项目的开发和维护效率,使得团队可以专心于业务开发,部门也陆续有了更多的产出。目前我们已经有大部门业务项目专向了goframe
。平台每日流量千万级别。
最后,感谢开源作者们的默默贡献!我们也在努力推动团队秉着来源社区,回馈社区的思想,未来也会更多参与社区贡献。