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 を作りその中に以下のコードを書き込みます。
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関数を以下にように書き換えます。
func main() {
e := InitializeEvent()
e.Start()
}
当然ですが、この時点で実行しても InitializeEvent 関数が定義されていないので失敗します。
この InitializeEvent 関数を自動的に作成するのがwireの役割となります。
次に、 wire.go を作り、以下の内容を書き込みます。
// +build wireinject
package main
import "github.com/google/wire"
func InitializeEvent() Event {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}
}
Providerを渡す順番はどの順番でもよく、wireがシグネチャを解析して適切な順番でコードを生成してくれます。
返り値は、コンパイラの型チェックを満たすために、空の構造体を返します。
この構造体に値を追加してもwireでは無視されるので注意が必要です。
また、このファイルは実ビルド時には必要ないファイルなので、ファイルの最初に Build Constraints を追加して除外しておきます。(空行も必ず入れます)
Build Constraints については下記を参考にしてください。https://golang.org/pkg/go/build/#hdr-Build_Constraints
DI用の関数を生成する
では、実際にwireコマンドを実行してDI用の InitializeEvent 関数をジェネレートします。
$ wire
wire: github.com/*****/*****: wrote /home/*****/github.com/*****/*****/wire_gen.go
無事生成されました。
// 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
}
これで、先程のmain関数から利用する準備が整いました。
ビルドして確認する
最後に、実際にビルドして実行してみます。
$ go build -o wire-test
$ ./wire-test
Hi there!
正常に動作しています。
まとめ
簡単にですが、チュートリアルを動作させて全体的な使い方が理解できたかと思います。
コード量が少ないと手動でDIしてもそれほど苦痛ではありませんが、コード量が増えてくると記述量が増えて辛くなってきます。 wireを導入することで記述量が減りコードを書く負担は下がると思います。
一方、ビルドステップと学習コストは増えます。
どの規模で導入するべきなのかをよく検討する必要があると思います。
この記事を書いた人
-
2008年にアーティスへ入社。
システムエンジニアとして、SI案件のシステム開発に携わる。
その後、事業開発部の立ち上げから自社サービスの開発、保守をメインに従事。
ドメイン駆動設計(DDD)を中心にドメインを重視しながら、保守可能なソフトウェア開発を探求している。
この執筆者の最新記事
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー