グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



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

今回は、GoでDI(dependency injection)してみたいと思います。

GoでDI用のパッケージはいくつかありますが、今回は「google/wire」を利用します。

GitHub 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 関数をコールすると、Eventが返ってくる想定です。
当然ですが、この時点で実行しても InitializeEvent 関数が定義されていないので失敗します。

この InitializeEvent 関数を自動的に作成するのがwireの役割となります。

次に、 wire.go を作り、以下の内容を書き込みます。

// +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 関数をジェネレートします。

$ 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
}
 wire_gen.go を開いてみると、mainパッケージ内にDI用の InitializeEvent 関数が定義されているのが確認できます。

これで、先程のmain関数から利用する準備が整いました。

ビルドして確認する

最後に、実際にビルドして実行してみます。

$ go build -o wire-test
$ ./wire-test
Hi there!

正常に動作しています。

まとめ

簡単にですが、チュートリアルを動作させて全体的な使い方が理解できたかと思います。

コード量が少ないと手動でDIしてもそれほど苦痛ではありませんが、コード量が増えてくると記述量が増えて辛くなってきます。 wireを導入することで記述量が減りコードを書く負担は下がると思います。
一方、ビルドステップと学習コストは増えます。

どの規模で導入するべきなのかをよく検討する必要があると思います。

この記事を書いた人

tkr2f
tkr2f事業開発部 web application engineer
2008年にアーティスへ入社。
システムエンジニアとして、SI案件のシステム開発に携わる。
その後、事業開発部の立ち上げから自社サービスの開発、保守をメインに従事。
ドメイン駆動設計(DDD)を中心にドメインを重視しながら、保守可能なソフトウェア開発を探求している。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします