Go では依存性注入(DI)のためのコードを自動生成するツールである wire が良く利用されるようでので、早速導入したいと思います。
チュートリアルを自分が今作っているマイクロブログに当てはめただけです。
DI導入前
導入前は以下のように、自前でインスタンスを生成して引数として渡していました。
func InitializeServer() *controllers.Server {
commentRepository := repositories.NewCommentRepositoryImpl()
commentUsecase := usecases.NewCommentUsecase(commentRepository)
server := controllers.NewServer(commentUsecase)
return server
}
これはこれでシンプルなのですが、repositoryやusecaseが増えてくると大変そうです。
wireは上記のようなソースコードを自動生成してくれるツールです。
wireのインストール
go install github.com/google/wire/cmd/wire@latest
wire.goを記述
wireは、wire.go
とそこで指定されているコンストラクタ関数から自動でDI用のコードを自動生成してくれます。
今回のケースでは、以下のようにwire.go
を記述しました。
wire.Build()
で各構造体のコンストラクタ関数を列挙しておくと、勝手にソースコードから関連性を把握してくれるようです。
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
"github.com/rinoguchi/microblog/adapters/controllers"
"github.com/rinoguchi/microblog/adapters/repositories"
"github.com/rinoguchi/microblog/usecases"
)
func InitializeServer() *controllers.Server {
wire.Build(
controllers.NewServer,
usecases.NewCommentUsecase,
repositories.NewCommentRepositoryImpl,
)
return &controllers.Server{}
}
DI用のコードを自動生成
以下のコマンドでDI用のコード(wire_gen.go)を自動生成します。
wire
> wire: github.com/rinoguchi/microblog: wrote /Users/xxx/xxx/microblog/wire_gen.go
トラブルシューティング
wireの設定や実装がおかしいとwire
実行時にエラーが発生します。
-
wire.Build
でコンストラクタ関数の指定が足らない場合wire > wire: /Users/xxx/xxx/microblog/wire.go:13:1: inject InitializeServer: no provider found for github.com/rinoguchi/microblog/entities.CommentRepository > needed by *github.com/rinoguchi/microblog/usecases.CommentUsecase in provider "NewCommentUsecase" (/Users/ryoichiinoguchi/workplace/microblog/usecases/comment_usecase.go:13:6) > needed by *github.com/rinoguchi/microblog/adapters/controllers.Server in provider "NewServer" (/Users/xxx/xxx/microblog/adapters/controllers/server.go:14:6) > wire: github.com/rinoguchi/microblog: generate failed > wire: at least one generate failure
wire_gen.goの内容
DI導入前に自前で実装していたコードと全く同じ内容が自動再生されていました。
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
import (
"github.com/rinoguchi/microblog/adapters/controllers"
"github.com/rinoguchi/microblog/adapters/repositories"
"github.com/rinoguchi/microblog/usecases"
)
// Injectors from wire.go:
func InitializeServer() *controllers.Server {
commentRepository := repositories.NewCommentRepositoryImpl()
commentUsecase := usecases.NewCommentUsecase(commentRepository)
server := controllers.NewServer(commentUsecase)
return server
}
main.goから呼び出し
最後に、mian.go
から対象のコードを呼び出せば完了です。
package main
import (
"fmt"
"net/http"
"os"
middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware"
"github.com/go-chi/chi/v5"
"github.com/rinoguchi/microblog/adapters/controllers"
)
func main() {
swagger, err := controllers.GetSwagger()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
os.Exit(1)
}
swagger.Servers = nil
server := InitializeServer() // <==== この部分
router := chi.NewRouter()
router.Use(middleware.OapiRequestValidator(swagger))
controllers.HandlerFromMux(server, router)
http.ListenAndServe(":8080", router)
}
アプリケーションの実行
以下のようにしてアプリケーションを実行します。
go run .
main.go
を指定すると、以下のようにwire.go
の内容が読み込まれずにエラーが発生します。
go run main.go
# command-line-arguments
./main.go:20:12: undefined: InitializeServer
ビルドしてから実行するのもありです。
# ビルド(microblogという実行ファイルが作成される)
go build
# 実行
microblog
さいごに
今回、実装したソースコードは以下のリポジトリで公開してます。
https://github.com/rinoguchi/microblog