挖坑系列之Fx初窥

当我们谈到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)

  1. 首先我们安装下Fxgo get go.uber.org/fx

  2. 初始化Fx

func main(){
  fx.New.Run()
}
  1. 现在我们需要通过将HTTP serve Mux加到New方法,这样就可以将它注入到我们的HTTP handler

func main(){
  fx.New(
    fx.Provide(http.NewServeMux),
  ).Run()
}
  1. 更新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"))
}
  1. 最后一步是启动我们的监听器

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的思想。

总结

依赖注入给我带来了相当好的价值,但是也存在缺点:

  1. 依赖注入会创建需要构造详细信息的客户端,当明显的默认值可用时,这可能会很困难

  2. 依赖注入将对象的构造和行为分开,代码也难以追踪

  3. 需要更多的前期工作

本文只是简单介绍了下Fx,计划后续有机会可以深入剖析一下,先挖个坑。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值