網頁

2023/10/30

Golang uber Fx 依賴注入套件 http Web範例

Go語言的三方依賴注入(Dependency Injection)套件uber-go/fx範例。


範例環境

  • Go 1.19
  • uber-go/fx v1.20.1


安裝

在專案根目錄執行go get go.uber.org/fx@v1安裝Fx。


事前要求

參考「Golang uber Fx 依賴注入套件 簡單範例」瞭解基本用法。


範例

本範例來自官方文件的範例,只做點小修改,可以直接參考Fx - Get started with Fx

Fx是透過在本身的運行環境中實現依賴注入,所以一開始必須用fx.New建立fx.App物件,然後呼叫fx.App.Run來運行Fx。

fx.New的參數中設定各種物件的建構式和調用函式。

fx.Provider用來註冊物件的建構式,告訴Fx如何建立依賴類型的實例,這些建構式的參數可能依賴於其他建構式的結果。例如NewHTTPServer的參數fx.Lifecycle實例由Fx本身提供,而*http.ServeMux的實例是由NewServeMux提供。

同樣地,NewServeMux的參數[]Route分別由實現Route介面的EchoHandler的建構式NewEchoHandlerHelloHandler的建構式NewHelloHandler提供。

fx.Annotate用來標注建構式參數,第一個參數為要被標注的建構式;第二個參數以後為要標注的選項。

  • fx.ParamTags用來標注建構式參數實例的名稱(`name:"..."`)、群組(`group:"..."`)或選填(`optional:"true"`)等,用來和建構式返回類型對應。
  • fx.ResultTags用來標注建構式返回實例的名稱(`name:"..."`)、群組(`group:"..."`)等,用來和建構式參數類型對應。
  • fx.As 用來標注建構式返回的實例為介面。

fx.Invoke在Fx執行時會立刻被調用,若有多個會依設定順序執行。

fx.Provider註冊的建構式順序不重要,且只有需要用到返回的類型時才會被調用,而為了讓在fx.Provide中註冊的NewHTTPServer建構式被調用,所以在fx.Invoke中去執行一個參數為*http.Server的空函式func(*http.Server) {}。也就是說func(*http.Server) {}需要注入*http.Server的實例,因此Fx才會去調用在fx.Provider註冊的NewHTTPServer建構式。

main.go

package main

import (
    "context"
    "fmt"
    "net"
    "net/http"

    "go.uber.org/fx"
)

func main() {
    fx.New(
        fx.Provide(
            NewHTTPServer,
            fx.Annotate(
                NewServeMux,
                fx.ParamTags(`group:"routes"`),
            ),
            AsRoute(NewEchoHandler),
            AsRoute(NewHelloHandler),
        ),
        fx.Invoke(func(*http.Server) {}),
    ).Run()
}

func AsRoute(f any) any {
    return fx.Annotate(
        f,
        fx.As(new(Route)),
        fx.ResultTags(`group:"routes"`),
    )
}

func NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux) *http.Server {
    srv := &http.Server{Addr: ":8080", Handler: mux}
    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            ln, err := net.Listen("tcp", srv.Addr)
            if err != nil {
                return err
            }
            fmt.Println("Starting HTTP server at", srv.Addr)
            go srv.Serve(ln)
            return nil
        },
        OnStop: func(ctx context.Context) error {
            return srv.Shutdown(ctx)
        },
    })
    return srv
}

type Route interface {
    http.Handler
    Pattern() string
}

type EchoHandler struct{}

func NewEchoHandler() *EchoHandler {
    return &EchoHandler{}
}

func (h *EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("echo"))
}

func (h *EchoHandler) Pattern() string {
    return "/echo"
}

type HelloHandler struct{}

func NewHelloHandler() *HelloHandler {
    return &HelloHandler{}
}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func (h *HelloHandler) Pattern() string {
    return "/hello"
}

func NewServeMux(routes []Route) *http.ServeMux {
    mux := http.NewServeMux()
    for _, route := range routes {
        mux.Handle(route.Pattern(), route)
    }

    return mux
}

github



沒有留言:

張貼留言