当我们谈到web服务时,使用Java的小伙伴第一反应肯定是Spring全家桶,虽然约定大于配置的SpringBoot相比最初Spring版本在易用性、开发效率方便已经有了长足的进步,但是其API还是相对较繁杂。因此欢迎大家来到golang的世界,这里的一切都尽可能简单。
实现一个可以与浏览器交互输出"hello world"的web服务的典型做法是启动一个HTTP服务并运行自定义的路由。以下代码片段,我们通过使用Go内置net/http
包不需要外部依赖即可实现。
Filename: server/server.go
import "net/http"
// Handler for http requests
type Handler struct {
mux *http.ServeMux
}
// New http handler
func New(s *http.ServeMux) *Handler {
h := Handler{s}
h.registerRoutes()
return &h
}
// RegisterRoutes for all http endpoints
func (h *Handler) registerRoutes() {
h.mux.HandleFunc("/", h.HelloWorld)
}
func (h *Handler) HelloWorld(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte("Hello World"))
}
---
Filename: main.go
package main
import (
"net/http"
"medium/web_server/server"
)
func main() {
mux := http.NewServeMux()
server.New(mux)
http.ListenAndServe(":8080", mux)
}
准备好上述代码,我们通过终端cd
到对应目录,执行go run main.go
即可发现我们的web服务已经ready。
到目前为止,我们已经完成一个简单的web服务器。但是实际开发过程中我们业务往往比较复杂,我们需要手动处理大量的依赖关系,这个时候我们就需要使用Fx(Uber构建的依赖注入框架)。
什么是依赖注入
依赖注入即使用控制反转来解决依赖的一种软件设计模式。依赖注入中,依赖
是一个能够被使用的对象。注入
是将依赖项传递给使用它的依赖对象。依赖注入是控制反转的子集。简单讲,依赖注入是对象合成的一种模式,父对象提供了子对象所需要的所有依赖关系。
三种依赖注入:
构造器注入
Setter方法注入
基于接口的注入
什么是Fx
根据Uber的官方文档(https://pkg.go.dev/go.uber.org/fx),Fx是一个Go的应用框架,主要解决两个问题:
让依赖更加容易
消除了对全局状态和
func init()
的需求
Fx使用的是构造器注入模式,现在我们把之前的web服务使用Fx进行重构,看看Fx如何让依赖变容易的。强烈建议可以先阅读下Fx官方文档(https://pkg.go.dev/go.uber.org/fx)
首先我们安装下Fx
go get go.uber.org/fx
初始化Fx
func main(){
fx.New.Run()
}
现在我们需要通过将
HTTP serve Mux
加到New
方法,这样就可以将它注入到我们的HTTP handler
func main(){
fx.New(
fx.Provide(http.NewServeMux),
).Run()
}
更新
HTTP handler
的代码
package httphandler
import "net/http"
// Handler for http requests
type Handler struct {
mux *http.ServeMux
}
// New http handler
func New(s *http.ServeMux) *Handler {
h := Handler{s}
h.registerRoutes()
return &h
}
// RegisterRoutes for all http endpoints
func (h *Handler) registerRoutes() {
h.mux.HandleFunc("/", h.HelloWorld)
}
// HelloWorld handler which recieves the user request
func (h *Handler) HelloWorld(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte("Hello World"))
}
最后一步是启动我们的监听器
func main() {
fx.New(
fx.Provide(http.NewServeMux),
fx.Invoke(server.New),
fx.Invoke(registerHooks),
).Run()
}
func registerHooks(
lifecycle fx.Lifecycle, mux *http.ServeMux,
) {
lifecycle.Append(
fx.Hook{
OnStart: func(ctx context.Context) error {
go http.ListenAndServe(":8080", mux)
return nil
},
},
)
}
模块化
实际开发过程中,上面的webserver显然是不够看。你往往需要写大量复杂的业务逻辑,那么为了你的业务职责单一,可复用,模块化或许是个不错的选择,比如你的系统中可能会抽象出日志模块,认证模块,统计模块等等。
而Fx就是基于模块化编程的概念设计的,当Fx创建一个新对象(对象A)时,它将查找对象A所需要的依赖项,
一种情况 不需要依赖项,在应用程序上下文中创建对象A
另一种情况 Fx在应用程序上下文中找到所需的依赖项,将其注入并创建对象A
举个栗子,假如我们现在要为刚刚的webserver提供一个日志记录的能力,我们可以利用zap
来构建一个日志模块。
// ProvideLogger to fx
func ProvideLogger() *zap.SugaredLogger {
logger, _ := zap.NewProduction()
slogger := logger.Sugar()
return slogger
}
// Module provided to fx
var Module = fx.Options(
fx.Provide(ProvideLogger),
)
然后更新main.go
func main() {
fx.New(
fx.Provide(http.NewServeMux),
fx.Invoke(server.New),
fx.Invoke(registerHooks),
loggerfx.Module,
).Run()
}
func registerHooks(
lifecycle fx.Lifecycle, mux *http.ServeMux, logger *zap.SugaredLogger,
) {
lifecycle.Append(
fx.Hook{
OnStart: func(context.Context) error {
logger.Info("Listening on localhost:8080")
go http.ListenAndServe(":8080", mux)
return nil
},
OnStop: func(context.Context) error {
return logger.Sync()
},
},
)
}
这样一个简单的module注入就完成了,我们也可以尝试将一个RPC调用模块化 然后应用下DI的思想。
总结
依赖注入给我带来了相当好的价值,但是也存在缺点:
依赖注入会创建需要构造详细信息的客户端,当明显的默认值可用时,这可能会很困难
依赖注入将对象的构造和行为分开,代码也难以追踪
需要更多的前期工作
本文只是简单介绍了下Fx,计划后续有机会可以深入剖析一下,先挖个坑。。。