Beego 2.0 初心
模块化与解耦
AOP 初次尝试
更好的可观测性
tracing 和 metrics
logging
防呆设计 ORM
姗姗来迟的配置模块优化
adapter 模块和升级指南
未来
Beego 2.0 初心
在鸽子精的本性屡次发作之后,我们终于官宣Beego v2.0要和大家见面了。
在得知我们开启了 Beego 2.0 的时候,很多人问我们,你们搞 2.0 干啥呀?
其实不论是 Beego 1.x 还是 Beego 2.0,我们的初心一直没有改变,也就是希望能够为 Go 企业级应用开发提供一种一站式的解决方案,尤其是我们希望能够为中小型企业赋能,帮助这些企业提升研发效率、工程质量,以快速推出新产品,快速完成迭代。
我们很多大型企业都有很多规范,但是我们中小企业在规范和基础组件上面经常是很混乱,开发早期都是怎么快怎么来,但是当有新同学加入或者有一个同学离职,就会导致相同功能不同的写法、引入同一功能的不同组件(例如日志引入logrus、zap), 最终会导致维护困难、重构难,遇到问题不知道怎么查找问题点,这些都是我们很多企业遇到的困难。
而站在我们打工人的角度,我们也相信,真正的劳动者,不应该是被禁锢在流水线上的囚犯。技术应当解放生产力,将劳动者者从繁琐枯燥的重复性劳动中解放出来,去从事具有创造性艺术性的活动。
所以,我们的设计目标,就是做一个“简单、高效、一站式”的公司统一应用框架。具体而言则是围绕这六个特征进行:
统一标准:让不同层次的工程师可以基于同样的标准写代码;
低代码化:自动生成重复代码,从 CRUD 中解脱出来,专注业务;
错误收敛:设计错误处理标准,大幅提升错误发现、排查和修复的效率;
可治理性:管理应用的生命周期,让应用能够在开发、测试、部署和运维阶段都更简单方便;
可插拔性:自由选择使用的组件,自由接入自己的组件;
可观测性:全方位观测应用,掌握应用的各个方面;
那么,和原先的 Beego 比起来有什么不同呢?让我们一起来看看 Beego 2.0 的几个重大变化吧!
模块化与解耦
在准备着手 v2.0 的时候,我们做了一些调查。一方面是在社区发放调查问卷,一方面是整理 Beego 的 issue,最终我们确认了 v2.0 的总体思路,那就是:模块化,模块化,模块化!
其实在 v1.x 的时候,模块化已经有了坚实的基础:
如图所示,很显然 Beego 是由这八个模块组成。而 Beego 被人诟病太重,也是因为 Beego 一个项目就具有这么多模块。而实际上,各个模块即便在 1.x 里面也是相当独立的,只有小部分会耦合在一起。
因此我们要做的是就是重新组织一下这些模块,并且将模块之间的耦合降到最低。
这方面我们参考了几个非常优秀的开源框架,重新划分了我们的模块组织:
正如上图所示,我们希望模块之间只依赖于底层的模块,而互相之间不会依赖。
对于一个企业来说,如果选择 Beego,那么它很容易就统一所有的技术选型(统一标准,扩展组件),并且带来极高的研发效能——这对大多数的中小型企业、初创企业来说至关重要。
AOP 初次尝试
AOP 是一个企业级应用离不开的场景。例如我们想要支持的更好的可观测性,那么在接入诸如 opentracing
, prometheus
的时候,就需要这种无侵入式的 AOP 解决方案。
我们思考过几个方案,比如说编译器接口,在编译阶段通过修改 AST 注入特定的 AOP 逻辑;又或者采用代理,但是这需要一个完整的依赖注入框架支持,从开发体验上来讲,也并不是特别好;
从理论上来说,我们喜欢第一种方案,即编译器接口的方案。可惜 Go 并未暴露这种编译器的接口,允许我们在编译过程中修改代码。
在无法使用这些手段的约束下,我们只能折中设计了一个能力有限的无侵入式的 AOP 方案。
那就是使用 filter-chain
的设计模式来完成这一类的 AOP 需求。
具体做法是,我们为主要模块设计了 filter-chain
。这些接口大抵类似,只是参数略有不同。例如在 httplib 中,其接口是:
type FilterChain func(next Filter) Filter
type Filter func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error)
用户很容易可以通过扩展 Filter
接口来完成自定义的功能,例如日志:
func myFilter(next httplib.Filter) httplib.Filter {
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
r := req.GetRequest()
logs.Info("hello, here is the filter: ", r.URL)
// 别忘了调用下一个filter
return next(ctx, req)
}
}
这个例子只是记录了一下请求 URL。实际上,如果你要实现熔断限流和容错处理,都可以借助这些 Filter
来完成。
目前,总的来说,AOP 是一个初次尝试的东西。当前的这种机制还不是特别通用。它被局限在一个模块暴露的公共 API 部分,也就是模块入口和出口部分。而且它也不是声明式的。我们所期望的,理想中的 AOP 应当是声明式的,更加贴近 Java Spring 那种声明式 AOP 框架。
更好的可观测性
在 Beego 1.x 中,可观测性三个方面, tacing
, metrics
和 logging
,其中 logging
已经得到了很好的支持。
tracing 和 metrics
我们的目标是支持 opentracing
和 prometheus
。选择 opentracing
,是因为它颇有一种业界标准的感觉。大多数有影响力的 tacing 框架,都会考虑提供 opentracing
API 的支持。这意味着,用户可以自行选择他们青睐的 tracing 框架,只需要在启动的时候注入特定的实现即可。
promethues
虽然不具备 opentracing
API 那种普适性。但是它已经是业界事实上的标准了。
还有一个候选者,就是 opentelemetry
。虽然它号称是下一代的 tracing
标准,但是目前来说,它还不具备 opentracing
那么大的影响力。它自身的实现也很粗糙,因此排除了它。
我们的重点目标是,让使用 Beego 的应用可以实现无缝连接。
例如,假如我用 Beego 同时有 A 和 B 两个应用,而且 A 会调用 B,那么我只需要开启了 tracing
功能,那么我在 A 上使用 httplib 调用 B 的 http 接口,就能实现端到端的链路监控。
而实际上,我们也达到了这个目标。例如我测试时候一个效果图,这里我选择了 zipkin
作为 opentracing
的实现:
logging
logging
方面,这一次我们也加进去了一个呼声很高的特性——自定义日志格式。自定义日期格式是一个很实用的功能,一方面是公司可能有日志格式方面的要求,一方面则是更好支持日志分析的需求。当然,品位,或者说个人偏好也一并满足了。
为了降低大家编写自定义日志格式支持的难度,我们额外提供了一个实现 PatternLogFormatter
。例如我们传入一个期望的日志格式 %F:%n|%w%t>> %m
那么最终输出的日志如:
/User/go/beego/main.go:10|2020-12-11[E]>> message
防呆设计 ORM
每次有人问你们为什么要设计新的 ORM 的事务接口?我们都是统一回答:因为用户频繁误用,导致我们回答 issue
不堪其扰。
那么原来的 ORM 有什么弊端呢?
原来的 ORM,如果按照文档来使用,是毫无破绽的。只不过原本的 ORM 是一个有状态的东西,比如说里面有一个 isTx
标记,标记创建的 ORM 对象当前是否处于一个事务中。
然后这就会导致,用户在不同的 goroutine 之中使用同一个 ORM 对象的时候,经常出现并发问题。
这一次我们就改进了这个问题。当然“防呆设计”是一个开玩笑的说法。毕竟一个 API 经常被误用,本身就说明了它在设计上是不够直观的。
我们这一次摒弃了原本维持 isTx
的做法,而是在开启一个事务的时候,返回一个全新的 TxOrm
。这个对象是一次性的,随取随用,用完即丢。
而且,为了减轻手动管理事务的负担,我们提供了一个利用闭包管理事务的 API,用起来很方便:
err := o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
// data
user := new(User)
user.Name = "test_transaction"
// insert data
// Using txOrm to execute SQL
_, e := txOrm.Insert(user)
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
姗姗来迟的配置模块优化
这一次我们在配置模块里面加入了三个很有意思的方法:
Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error
Sub(key string) (Configer, error)
OnChange(key string, fn func(value string))
说起来这三个方法也只能算是终于把应该有的东西支持了。
不过我们还是想要额外提及一下, Unmarshaler
+ Sub
简直是神器,能够让用户非常简单的就按照自己的模块组织配置,而后将这些配置反序列化为特定对象。
并且,我们这一次终于迎来第一个远程配置中心的实现 etcd
。用户只需要引入 etcd
的实现,就可以使用了!
其它远程配置中心的支持,欢迎大家来提 PR 啊!
adapter 模块和升级指南
这一次,因为项目结构发生了重大变更。为了减轻升级的负担,我们特意开发了一个 adapter
模块。该模块的结构和原有的结构一样,只不过所有的内部实现都用 2.x 的 API。
用户只需要将包名从 v1.x 的包名换成 adapter
的包名就可以了。
这种替换,我们在 bee
里面也做了支持,只需要执行:
bee fix -t 2
未来
我们的愿景是成为“最简单易用的企业级应用开发框架!”。
接下来我们的工作也会以前面提到的六个特征作为指导,围绕以下方面进行:
统一标准:着重解决当前业务开发测试难的问题,为 ORM 等模块提供便利的测试支持;
错误收敛:为全部组件设计统一的错误码和标准化的错误信息;
可治理性:增强现有的治理功能,例如暴露
cache
的统计信息,模块的错误信息。同时支持 Beego 应用集群维度监控和治理能力;可观测性:继续在 Beego 内部加多埋点和日志信息,并且允许设定参数开启或者关闭(包括部分开启和部分关闭)。也希望能够提供全面的 benchmark 测试,对所有模块的性能做到心中有数;
技术方向
从技术发展方向而言,我们的三个核心词汇就是:泛型,容器(DI),云原生。
泛型会是我们的第一个目标。因为,每一次泛型出现,都会导致已有的模式发生巨大的变化。例如说现在的 ORM 接口几乎就是只接收 interface
对象,基本上无法享受到编译期的检查。我们也希望我们能够在泛型出来之时,能够迅速抢占技术高地,对外输出我们的实践标准。
容器,包括依赖注入,应用生命周期管理,AOP 方面,会是我们的第二个目标。AOP 这是为了将用户从无聊的创建对象,管理对象和销毁对象中解放出来。而 AOP,前面已经提及了,这是一个做企业级应用开发框架不得不解决的一个问题。
第三个目标云原生则是,探索如何利用好云原生的技术,增强 Beego 应用的可用性、稳定性,减轻对 Beego 应用的运维管理负担。
在此我们先立一个 Flag,希望泛型正式发布的时候,我们已经做好准备了。
致谢
感谢这些为 Beego2.0 做出贡献的人员(Github ID):
flycash, jianzhiyao, AllenX2018, IamCathal, tayoogunbiyi, wangle201210, mlboy, askuy, flutterWang, higker, weirubo, yitea, Cadenguo, unknowon, vinicio, ACrushTest.
还有那些未列出来但是参与我们社区讨论,在 issue 里面给我们提出很多建议的人。
特别感谢 askuy,他可以说是 Beego 2.0 的总架构师,他丰富的经验,使我们更有底气,更有自信。
尤其感谢谢大,为我们把控方向,给予了我们充分的信任,让我们能够在 Beego 里面大展身手。
最后要感谢我们的社区,给了我们很多有用的建议和反馈。以及对我们鸽了好几次的无限包容(我们还是希望你们多来催催我们不然就更加容易鸽了)。
欢迎大家来使用我们 Beego!我们准备了文档和例子,还等什么, 一起来啊!
- 官网: https://beego.me
- 例子 https://github.com/beego/beego-example
- Release Note:https://github.com/beego/beedoc/blob/master/en-US/intro/releases.md