google/wireを使ってGoでDI(dependency injection)してみる

今回は、GoでDI(dependency injection)してみたいと思います。
GoでDI用のパッケージはいくつかありますが、今回は「google/wire」を利用します。
フローを把握する
wireを使ってのDIは、以下のフローになります。
- 「DIしたい対象を生成する関数」を生成する関数を定義する
- wireコマンドで、上記のファイルから「DIしたい対象を生成する関数」をジェネレートする
- その関数を利用する
他の言語のDIコンテナと比べると、すこしフローが違うので最初は戸惑うかもしれません。
DIコンテナ(とその設定)を、メタ的に自動生成するイメージに近いかと思います。
チュートリアルで動きを確認する
こちらの公式のチュートリアルを動かしながら動作を確認していきます。
https://github.com/google/wire/blob/master/_tutorial/README.md
まずは、手動でDIするコードで動きを理解しておきます。(予習は大事です)
main.go を作りその中に以下のコードを書き込みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package main import ( "fmt" ) type Message string func NewMessage() Message { return Message("Hi there!") } func NewGreeter(m Message) Greeter { return Greeter{Message: m} } type Greeter struct { Message Message // <- adding a Message field } func (g Greeter) Greet() Message { return g.Message } func NewEvent(g Greeter) Event { return Event{Greeter: g} } type Event struct { Greeter Greeter // <- adding a Greeter field } func (e Event) Start() { msg := e.Greeter.Greet() fmt.Println(msg) } func main() { message := NewMessage() greeter := NewGreeter(message) event := NewEvent(greeter) event.Start() } |
EventはGreeterに依存していて、GreeterはMessageに依存しています。
そして、main関数内でそれぞれの構造体を作り、手動でセットしています。
wireでDIするよう設定する
次に、先程のコードのmain関数を以下にように書き換えます。
1 2 3 4 5 |
func main() { e := InitializeEvent() e.Start() } |
InitializeEvent 関数をコールすると、Eventが返ってくる想定です。
当然ですが、この時点で実行しても InitializeEvent 関数が定義されていないので失敗します。
この InitializeEvent 関数を自動的に作成するのがwireの役割となります。
次に、 wire.go を作り、以下の内容を書き込みます。
1 2 3 4 5 6 7 8 9 10 |
// +build wireinject package main import "github.com/google/wire" func InitializeEvent() Event { wire.Build(NewEvent, NewGreeter, NewMessage) return Event{} } |
wire.Build に、各構造体を生成する関数(Provider)を渡します。
Providerを渡す順番はどの順番でもよく、wireがシグネチャを解析して適切な順番でコードを生成してくれます。
返り値は、コンパイラの型チェックを満たすために、空の構造体を返します。
この構造体に値を追加してもwireでは無視されるので注意が必要です。
また、このファイルは実ビルド時には必要ないファイルなので、ファイルの最初に Build Constraints を追加して除外しておきます。(空行も必ず入れます)
Build Constraints については下記を参考にしてください。https://golang.org/pkg/go/build/#hdr-Build_Constraints
DI用の関数を生成する
では、実際にwireコマンドを実行してDI用の InitializeEvent 関数をジェネレートします。
1 2 |
$ wire wire: github.com/*****/*****: wrote /home/*****/github.com/*****/*****/wire_gen.go |
無事生成されました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Code generated by Wire. DO NOT EDIT. //go:generate wire //+build !wireinject package main // Injectors from wire.go: func InitializeEvent() Event { message := NewMessage() greeter := NewGreeter(message) event := NewEvent(greeter) return event } |
wire_gen.go を開いてみると、mainパッケージ内にDI用の InitializeEvent 関数が定義されているのが確認できます。
これで、先程のmain関数から利用する準備が整いました。
ビルドして確認する
最後に、実際にビルドして実行してみます。
1 2 3 |
$ go build -o wire-test $ ./wire-test Hi there! |
正常に動作しています。
まとめ
簡単にですが、チュートリアルを動作させて全体的な使い方が理解できたかと思います。
コード量が少ないと手動でDIしてもそれほど苦痛ではありませんが、コード量が増えてくると記述量が増えて辛くなってきます。 wireを導入することで記述量が減りコードを書く負担は下がると思います。
一方、ビルドステップと学習コストは増えます。
どの規模で導入するべきなのかをよく検討する必要があると思います。

tkr2f

最新記事 by tkr2f (全て見る)
- <Goのパッケージ放浪記> ioパッケージに定義されている「Writerインターフェイス」について - 2020年11月24日
- <Goのパッケージ放浪記> ioパッケージに定義されている「Readerインターフェイス」について - 2020年10月23日
- google/wireを使ってGoでDI(dependency injection)してみる - 2020年8月6日
- GKEにArgo CDを導入してアプリケーションをデプロイする - 2020年7月9日
- WSL2のDebianにAnsibleの最新版をインストールする - 2020年6月16日
おすすめ関連記事
最新記事