※ソース記事はこちら
ここではFxの基礎を紹介する。このチュートリアルで、以下を行う。
- 空のアプリケーションを開始
- HTTPサーバーを追加
- サーバーにハンドラーを登録
- アプリケーションにロギングを追加
- ハンドラの結びつきを離すようリファクタリング
- サーバーに別のハンドラーの追加
- 実装の一般化
初めに、チュートリアルの残りのためにセットアップをする。
- 新しい空のプロジェクトを始める。
mkdir fxdemo cd fxdemo go mod init example.com/fxdemo
- 最新バージョンのFxをインストールする。
go get go.uber.org/fx@latest
最小限のアプリケーションの作成
Fxアプリケーションのhello-world相当のものをビルドしよう。このアプリケーションは、一連のログを出力する以外はまだ何もしない。
- 最小限の
main.go
を書く。package main import "go.uber.org/fx" func main() { fx.New().Run() }
- アプリケーションを実行する。
以下のような出力が見えるだろう。
go run .
これはFxアプリケーションにデフォルトで提供されているオブジェクトを表示しているが、まだ何も意味のあるものではない。[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1() [Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm() [Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm() [Fx] RUNNING
CTRL+C
でアプリケーションを終了する。[Fx] RUNNING ^C [Fx] INTERRUPT
ここでしたこと
引数無しのFx.New
を呼ぶことで空のFxアプリケーションをビルドした。アプリケーションは通常、コンポーネントをセットアップするために、引数を渡す。
その後、App.Run
メソッドでアプリケーションを実行した。このメソッドは終了するためのシグナルを受け取るまでブロックし、その後、終了する前に必要な何らかのクリーンナップ操作を実行する。
Fxは主に、長時間実行するサーバーアプリケーションを意図している。つまりこれらのアプリケーションは通常、シャットダウンするときに配布システムからシグナルを受け取る。
HTTPサーバーの追加
前のセクションで、何もしない最小限のFxアプリケーションを書いた。それに対してHTTPサーバーを追加しよう。
- HTTPサーバーを立ち上げる関数を書く。
これは十分ではなく、HTTPサーバーの開始方法をFxに伝える必要がある。追加の
// NewHTTPServerはHttpサーバーを立ち上げ、Fxアプリケーションが開始すると、要求を受け付け始める。 func NewHTTPServer(lc fx.Lifecycle) *http.Server { srv := &http.Server{Addr: ":8080"} return srv }
fx.Lifecycle
引数がそのためにある。 -
fx.Lifecycle
オブジェクトとともにアプリケーションに対してlifecycleフックを追加する。これはFxにHTTPサーバーの開始と終了方法を伝える。func NewHTTPServer(lc fx.Lifecycle) *http.Server { srv := &http.Server{Addr: ":8080"} 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 }
- 上のFxアプリケーションに対し、
fx.Provide
でこちらを提供する。func main() { fx.New( fx.Provide(NewHTTPServer), ).Run() }
- アプリケーションを実行する。
え?何がおかしいかっただろうか?出力の最初の行は、サーバーが提供されたことを表しているが、"Starting HTTP server"メッセージは含まれていない。サーバーは実行されなかった。
[Fx] PROVIDE *http.Server <= main.NewHTTPServer() [Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1() [Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm() [Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm() [Fx] RUNNING
- それを修正するには、起動したサーバーを要求する
fx.Invoke
を追加する。fx.New( fx.Provide(NewHTTPServer), fx.Invoke(func(*http.Server) {}), ).Run()
- 再度アプリケーションを実行する。今回は"Starting HTTP server"メッセージが出力に見えるに違いない。
[Fx] PROVIDE *http.Server <= main.NewHTTPServer() [Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1() [Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm() [Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm() [Fx] INVOKE main.main.func1() [Fx] HOOK OnStart main.NewHTTPServer.func1() executing (caller: main.NewHTTPServer) Starting HTTP server at :8080 [Fx] HOOK OnStart main.NewHTTPServer.func1() called by main.NewHTTPServer ran successfully in 7.958µs [Fx] RUNNING
- 実行中のサーバーに要求を送る。
リクエストは4o4なぜなら、サーバーはそれを扱う方法をまだ知らないからである。次のセクションでそれを修正する。
$ curl http://localhost:8080 404 page not found
- アプリケーションを停止する。
^C [Fx] INTERRUPT [Fx] HOOK OnStop main.NewHTTPServer.func2() executing (caller: main.NewHTTPServer) [Fx] HOOK OnStop main.NewHTTPServer.func2() called by main.NewHTTPServer ran successfully in 129.875µs
ここでしたこと
アプリケーションにHTTPサーバーを追加するためにfx.Provide
を使った。サーバーはFxアプリケーションライフサイクルに接続する。App.Run
が呼び出されると、要求を処理し始め、アプリケーションが終了シグナルを受け取ると、実行を停止する。HTTPサーバーが、アプリケーション内の他のコンポーネントから直接参照されていなくても、常にインスタンス化されるように、fx.Invoke
を使う。
ハンドラの登録
サーバーがリクエストを受け取ることができるように作ったが、まだそれを扱う方法を知らない。修正しよう。
- 基本的なHTTPハンドラを定義し、到着するリクエストボディをレスポンスにコピーする。ファイルの末尾に次のように追加する。
これをアプリケーションにProvideする。
// Echoハンドラは、リクエストボディをコピーし、レスポンスに戻すhttp.Handler. type EchoHandler struct{} // NewEchoHandlerは新しいEchoハンドラを作る. func NewEchoHandler() *EchoHandler { return &EchoHandler{} } // ServeHTTPはHTTPリクエストを/echoエンドポイントで処理する。 func (*EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if _, err := io.Copy(w, r.Body); err != nil { fmt.Fprintln(os.Stderr, "Failed to handle request:", err) } }
fx.Provide( NewHTTPServer, NewEchoHandler, ), fx.Invoke(func(*http.Server) {}),
- 次に
*http.ServeMux
を作る関数を書く。*http.ServeMux
は、サーバーによって受け取られたリクエストを異なるハンドラにルーティングする。まず始めに/echo
に送られたリクエストを*EchoHandler
にルーティングするため、そのコンストラクタは引数として*EchoHandler
を受け取るべきである。同様に、これをアプリケーションにProvideする。// NewServeMuxはServeMuxを作り、リクエストを与えられたEchoHandlerにルーティングする. func NewServeMux(echo *EchoHandler) *http.ServeMux { mux := http.NewServeMux() mux.Handle("/echo", echo) return mux }
注意すべきは、fx.Provide( NewHTTPServer, NewServeMux, NewEchoHandler, ),
NewServeMux
は上のNewEchoHandler
に追加された。fx.Provide
内に与えられたコンストラクタの順序は重要ではない。 - 最後に
NewHTTPServer
関数を修正し、この*ServeMux
へサーバーを接続する。func NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux) *http.Server { srv := &http.Server{Addr: ":8080", Handler: mux} lc.Append(fx.Hook{
- サーバーを実行する。
[Fx] PROVIDE *http.Server <= main.NewHTTPServer() [Fx] PROVIDE *http.ServeMux <= main.NewServeMux() [Fx] PROVIDE *main.EchoHandler <= main.NewEchoHandler() [Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1() [Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm() [Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm() [Fx] INVOKE main.main.func1() [Fx] HOOK OnStart main.NewHTTPServer.func1() executing (caller: main.NewHTTPServer) Starting HTTP server at :8080 [Fx] HOOK OnStart main.NewHTTPServer.func1() called by main.NewHTTPServer ran successfully in 7.459µs [Fx] RUNNING
- サーバーにリクエストを送る。
$ curl -X POST -d 'hello' http://localhost:8080/echo hello
ここでしたこと
fx.Provide
で、より多くのコンポーネントを追加した。これらのコンポーネントはコンストラクタにパラメータを追加することで、お互いに依存性を宣言している。Fxは与えられた関数のパラメータと戻り値によってコンポーネントの依存性を解決する。
ロガーの追加
アプリケーションは現在、標準出力に"Starting HTTP server"メッセージを出力し、標準エラーにエラーを出力している。標準出力と標準エラー出力はどちらもグローバル状態でもある。ロガーオブジェクトに出力すべきである。
チュートリアルのこのセクションでは、Zapを使うが、どのようなロギングシステムも使うことができるはずである。
- ZapロガーをアプリケーションにProvideする。このチュートリアルでは、
zap.NewExample
を使うが、本番のアプリケーション用にはzap.NewProduction
を使うか、よりカスタマイズされたロガーを作るべきである。fx.Provide( NewHTTPServer, NewServeMux, NewEchoHandler, zap.NewExample, ),
-
EchoHandler
上でロガーを保持するフィールドを追加し、NewEchoHandler
内でこのフィールドにセットするため、新しいロガー引数を追加する。type EchoHandler struct { log *zap.Logger } func NewEchoHandler(log *zap.Logger) *EchoHandler { return &EchoHandler{log: log} }
-
EchoHandler.ServeHTTP
メソッド内で、標準エラー出力に出力する代わりにロガーを使う。func (h *EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if _, err := io.Copy(w, r.Body); err != nil { h.log.Warn("Failed to handle request", zap.Error(err)) } }
- 同様に、
NewHTTPServer
を更新し、ロガーが来ることを期待し、それに対して"Starting HTTP server"メッセージをロギングする。func NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux, log *zap.Logger) *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 } log.Info("Starting HTTP server", zap.String("addr", srv.Addr)) go srv.Serve(ln)
- (任意)同様にFx自身のログのために同じロガーを使うことができる。
これは
func main() { fx.New( fx.WithLogger(func(log *zap.Logger) fxevent.Logger { return &fxevent.ZapLogger{Logger: log} }),
[FX]
メッセージをロガーに出力するメッセージに置き換える。 - アプリケーションを起動する。
{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"} {"level":"info","msg":"provided","constructor":"main.NewServeMux()","type":"*http.ServeMux"} {"level":"info","msg":"provided","constructor":"main.NewEchoHandler()","type":"*main.EchoHandler"} {"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"} {"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"} {"level":"info","msg":"invoking","function":"main.main.func2()"} {"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"} {"level":"info","msg":"Starting HTTP server","addr":":8080"} {"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"6.292µs"} {"level":"info","msg":"started"}
- それに対してリクエストをポストする。
$ curl -X POST -d 'hello' http://localhost:8080/echo hello
ここでしたこと
fx.Provide
でアプリケーションにもう一つのコンポーネントを追加し、メッセージを出力するために必要な別のコンポーネントの中にそれをインジェクトした。そのために、コンストラクタに新しいパラメータを追加する必要があっただけだった。
任意のステップでは、Fxに対してFx自身の処理のためのカスタムロガーを提供したいと命じた。既存のfxevnt.ZapLogger
を使い、インジェクトされたロガーからカスタムロガーを作った。そうすることで、すべてのログは同じフォーマットに従う。
登録の分離
上のNewServeMux
は、EchoHandler
上で明示的に依存して宣言している。これは不必要な緊密な結びつきである。NewServeMux
は正確なハンドラ実装を本当に知る必要はあるだろうか?NewServeMux
のためにテストを書きたいとき、EchoHandler
を構築する必要はあるべきではない。
これを修正しよう。
- main.goに
Route
型を定義する。これはhttp.Handler
の拡張であり、そこでハンドラはその登録パスを知る。// Routeは一つのhttp.Handlerであり、そこに登録されたmuxパターンを知っている。 type Route interface { http.Handler // Patternは登録されているパスを報告する。 Pattern() string }
-
EchoHandler
を修正し、このインターフェイスを実装する。func (*EchoHandler) Pattern() string { return "/echo" }
-
main()
内でNewEchoHandler
エントリをアノテーションし、ハンドラがRouteとしてProvideすべきことを明示する。fx.Provide( NewHTTPServer, NewServeMux, fx.Annotate( NewEchoHandler, fx.As(new(Route)), ), zap.NewExample, ),
-
NewServeMux
を修正し、Routeを受け取り提供されているパターンを使用する。// NewServeMuxはServeMuxを作り、リクエストを与えられたRouteに対してルーティングする func NewServeMux(route Route) *http.ServeMux { mux := http.NewServeMux() mux.Handle(route.Pattern(), route) return mux }
- アプリケーションを起動する。
{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"} {"level":"info","msg":"provided","constructor":"main.NewServeMux()","type":"*http.ServeMux"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewEchoHandler(), fx.As([[main.Route]])","type":"main.Route"} {"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"} {"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"} {"level":"info","msg":"invoking","function":"main.main.func2()"} {"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"} {"level":"info","msg":"Starting HTTP server","addr":":8080"} {"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"10.125µs"} {"level":"info","msg":"started"}
- それに対してリクエストを送る。
$ curl -X POST -d 'hello' http://localhost:8080/echo hello
ここでしたこと
利用者から実装を分離するインターフェイスを導入した。その後、Provideされたコンストラクタをfx.Annoate
でアノテーションし、fx.As
でその結果をインターフェイスにキャストした。そうすることで、NewEchoHandler
は*EchoHandler
を戻し続けることができた。
もう一つのハンドラの登録
上で定義したハンドラは単一のハンドラを持っている。別のものを追加しよう。
- 同じファイル内で新規のハンドラを作る。
// HelloHandlerはHTTP handlerであり、ユーザーに挨拶を出力する。 type HelloHandler struct { log *zap.Logger } // NewHelloHandlerは新規のHelloHandlerを作る。 func NewHelloHandler(log *zap.Logger) *HelloHandler { return &HelloHandler{log: log} }
- このハンドラに
Route
インターフェイスを実装する。このハンドラはリクエストボディを読み、呼び出し元にようこそメッセージを書き出す。func (*HelloHandler) Pattern() string { return "/hello" } func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { h.log.Error("Failed to read request", zap.Error(err)) http.Error(w, "Internal server error", http.StatusInternalServerError) return } if _, err := fmt.Fprintf(w, "Hello, %s\n", body); err != nil { h.log.Error("Failed to write response", zap.Error(err)) http.Error(w, "Internal server error", http.StatusInternalServerError) return } }
- これをアプリケーションに、
NewEchoHandler
の隣に、Route
としてProvideする。fx.Annotate( NewEchoHandler, fx.As(new(Route)), ), fx.Annotate( NewHelloHandler, fx.As(new(Route)), ),
- アプリケーションを実行する。サービスが開始に失敗する。
多くの出力があるが、エラーメッセージ内に、以下の出力があることがわかる。
[Fx] PROVIDE *http.Server <= main.NewHTTPServer() [Fx] PROVIDE *http.ServeMux <= main.NewServeMux() [Fx] PROVIDE main.Route <= fx.Annotate(main.NewEchoHandler(), fx.As([[main.Route]]) [Fx] Error after options were applied: fx.Provide(fx.Annotate(main.NewHelloHandler(), fx.As([[main.Route]])) from: [...] [Fx] ERROR Failed to start: the following errors occurred: - fx.Provide(fx.Annotate(main.NewHelloHandler(), fx.As([[main.Route]])) from: [...] Failed: cannot provide function "main".NewHelloHandler ([..]/main.go:53): cannot provide main.Route from [0].Field0: already provided by "main".NewEchoHandler ([..]/main.go:80)
これはFxがアノテーション無しで、コンテナ内に同じ型の二つのインスタンスが許可されていないため、失敗している。cannot provide main.Route from [0].Field0: already provided by "main".NewEchoHandler ([..]/main.go:80)
NewServeMux
はどのRoute
を使うかわからない。修正しよう。 -
NewEchoHandler
とNewHelloHandler
をmain()
内で両方のハンドラのために名前でアノテーションする。fx.Annotate( NewEchoHandler, fx.As(new(Route)), fx.ResultTags(`name:"echo"`), ), fx.Annotate( NewHelloHandler, fx.As(new(Route)), fx.ResultTags(`name:"hello"`), ),
- もう一つのRouteパラメータを
NewServeMux
に追加する。// NewServeMuxはServeMuxを作り、リクエストを与えられたRouteに対してルーティングする func NewServeMux(route1, route2 Route) *http.ServeMux { mux := http.NewServeMux() mux.Handle(route1.Pattern(), route1) mux.Handle(route2.Pattern(), route2) return mux }
-
main()
内でNewServeMux
が二つの名前の値をピックするようにアノテーションする。fx.Provide( NewHTTPServer, fx.Annotate( NewServeMux, fx.ParamTags(`name:"echo"`, `name:"hello"`), ),
- プログラムを起動する。
{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewServeMux(), fx.ParamTags([\"name:\\\"echo\\\"\" \"name:\\\"hello\\\"\"])","type":"*http.ServeMux"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewEchoHandler(), fx.ResultTags([\"name:\\\"echo\\\"\"]), fx.As([[main.Route]])","type":"main.Route[name = \"echo\"]"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewHelloHandler(), fx.ResultTags([\"name:\\\"hello\\\"\"]), fx.As([[main.Route]])","type":"main.Route[name = \"hello\"]"} {"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"} {"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"} {"level":"info","msg":"invoking","function":"main.main.func2()"} {"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"} {"level":"info","msg":"Starting HTTP server","addr":":8080"} {"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"56.334µs"} {"level":"info","msg":"started"}
- リクエストを送る。
$ curl -X POST -d 'hello' http://localhost:8080/echo hello $ curl -X POST -d 'gopher' http://localhost:8080/hello Hello, gopher
ここでしたこと
既存の型と同じ型を持つ値を生成するコンストラクタを追加した。コンストラクタをfx.ResultTag
でアノテーションし、名前つきの値を生成し、利用者はfx.ParamTag
で名前付きの値を利用した。
多くのハンドラを登録
前回のセクションでは、二つのハンドラを追加したが、NewServeMux
を作る際に、それらを明示的に名前で参照している。さらにハンドラを追加する場合に、これはすぐに不便になるだろう。
NewServeMux
がハンドラの数や名前がわからない場合、代わりに登録すべきハンドラのリストを単に受け取るのが好ましい。
やってみよう。
-
NewServeMux
を修正し、Route
オブジェクトのリストを操作するように修正する。func NewServeMux(routes []Route) *http.ServeMux { mux := http.NewServeMux() for _, route := range routes { mux.Handle(route.Pattern(), route) } return mux }
-
main
内のNewServeMux
エントリをアノテーションし、"routes"グループの内容を含むスライスを受け取るよう明示する。fx.Provide( NewHTTPServer, fx.Annotate( NewServeMux, fx.ParamTags(`group:"routes"`), ),
- 新規関数
AsRoute
を定義し、このグループをフェードする関数を作る。// AsRouteは与えられたコンストラクタに対して、"routes"グループにルートを提供することを宣言するアノテーションである。 func AsRoute(f any) any { return fx.Annotate( f, fx.As(new(Route)), fx.ResultTags(`group:"routes"`), ) }
-
main
内のNewEchoHandler
とNewHelloHandler
のコンストラクタをAsRoute
でラップし、このグループ内でそれぞれが自分のルートをフィードするようにする。fx.Provide( AsRoute(NewEchoHandler), AsRoute(NewHelloHandler), zap.NewExample, ),
- 最後にアプリケーションを起動する。
{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewServeMux(), fx.ParamTags([\"group:\\\"routes\\\"\"])","type":"*http.ServeMux"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewEchoHandler(), fx.ResultTags([\"group:\\\"routes\\\"\"]), fx.As([[main.Route]])","type":"main.Route[group = \"routes\"]"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewHelloHandler(), fx.ResultTags([\"group:\\\"routes\\\"\"]), fx.As([[main.Route]])","type":"main.Route[group = \"routes\"]"} {"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"} {"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"} {"level":"info","msg":"invoking","function":"main.main.func2()"} {"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"} {"level":"info","msg":"Starting HTTP server","addr":":8080"} {"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"5µs"} {"level":"info","msg":"started"}
- リクエストを送る。
$ curl -X POST -d 'hello' http://localhost:8080/echo hello $ curl -X POST -d 'gopher' http://localhost:8080/hello Hello, gopher
ここでしたこと
NewServeMux
をアノテーションし、スライスとして値グループを利用し、既存のハンドラのコンストラクタをこの値グループ内でフィードするようにアノテーションした。アプリケーション内の他のどのようなコンストラクタも、結果がRoute
インターフェイスに適合する限り、この値グループ内で値をフィードすることができる。それらはすべてお互いに収集され、ServeMux
コンストラクタに渡される。
参照リソース
- 値グループ 値グループのさらなる説明と、使い方