Closed6

GoのWireについて理解する

OsamuOsamu

到達していたい状態

  • Wireとは何かを人に説明できる
  • Wireを利用したコードを書ける

やること

  • Wireについて調べる
  • Wireのドキュメントを読む、手を動かす
OsamuOsamu

wireとは?

wireはDIツールの1つ。
注入する実装ををメインの処理に書くとかなりのコード数になるのと管理が大変なので、DIツールを呼び出すだけにして実装コストを下げる目的。

注入時にもインスタンス同士の依存関係とかも気にする必要があるので、それをよしなにやってくれるツール

DI を愚直に実装しようとすると依存関係の管理が煩雑になります。つまり、どのロジックがどのモジュールを必要としていて、どのような順番でインスタンスを生成・初期化していけばよいか、というかなり面倒なことを考える必要が出てくるのです。
ここで活躍するのが wire で、各ロジックやモジュールの依存関係を適切に整理・解決したコードを生成します。

wireを使う時にで押さえておくべき概念

1. Provider

初期化関数のこと。
特定の型のインスタンスを提供する関数。
→ いわゆる、New****()みたいなインスタンスを初期化する関数のことですね。

Providerはwire側でも手動で定義できますし、既存のコードにあるものを使用する形でも問題ないです。

2. Injector

プロバイダーを使って依存関係を解決し、必要なコンポーネントを初期化する関数のこと。
→ Providerを組み立てて、オブジェクトが注入されたインスタンスを返してあげる

インジェクターの役割としては以下が上げられます。

  • 依存関係を解決して、最終的なインスタンスを返す
  • プロバイダーを呼び出す順序を自動で管理する
  • 手動の初期化手順を簡略化する

パッケージ管理ツールのDI版みたいなイメージですかね

OsamuOsamu

DIツールとは?

DIを実現するにあたってサポートしてくれるツールのこと。
「DIコンテナ」といったりもする。

以下の記事を見たらわかりやすいのですが、メインからDIツール(DIコンテナ)を呼び出すだけでDIが実現する感じ

OsamuOsamu

wireのチュートリアルをやって、基本的な使い方を見てみる

こちらのWire Tutorialを見ながら使い方を見てみる

Using Wire to Generate Code

wireを使用して依存性注入の処理を簡単にしてみよう

One downside to dependency injection is the need for so many initialization steps. Let's see how we can use Wire to make the process of initializing our components smoother.

まずmain関数で以下のようにInitializeEventを定義

main.go
func main() {
    e := InitializeEvent()

    e.Start()
}

次にwire.goを作成し、InitializeEvent関数(Injector)を作成する。
wire.Build()の中にProviderを渡す。

また、返り値はInjectorで初期化したいインスタンスの初期値を返してあげる。
→ 今回だとEventの情報をInjectorで初期化したいので、返り値はEvent{}

wire.go
func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}

wire.goは自動生成の元ファイルで、ビルドには含めたくないのでファイル先頭に以下を記述

wire.go
//+build wireinject

wireのインストール

% go install github.com/google/wire/cmd/wire@latest

同じディレクトリにて以下を実行。
WireはInjectorと、Injectorが必要としているProviderも探しにいき、必要な初期化ステップを含んだ関数を自動で作成してくれる。

% wire

以下のようにwire_gen.goが作成される

wire_gen.go
// wire_gen.go

func InitializeEvent() Event {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)
    return event
}

wire.goのInjectorはビルドされないようにしているので、プログラムで実際にビルドされるのはwire_gen.goとなる。(メインで使用するのはwire_gen.goのInjector)

OsamuOsamu

User Guideの気になるところを見てみる

Providerの書き方

基本的にはイメージしているコンストラクタの書き方と同じです

  1. 他のパッケージからProviderを呼ぶには、通常のコードと同じようにエクスポートされる状態じゃないといけないので注意が必要。(最初が大文字)
  2. Provider自体も引数を受け取ることができる
  3. Providerはエラーを返すことができる
  4. Providerをグループ化したProvider Setsを作成することができる
    • var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)
  5. Provider Setsをさらにグループ化することができる
    • var MegaSet = wire.NewSet(SuperSet, pkg.OtherSet)

Injectorの書き方

  1. Injectorはwire.Build()を使用した関数の宣言を行う
  2. Injectorの戻り値は重要ではなく、ダミーの初期化されたコンストラクタでok
    • コンパイラのために指定をする必要があって、Wireが自動生成する時には無視される
  3. Wireは実行時依存がない
    • Wireで自動生成されたコードはそのままWireなしでもGoのプログラムとして動く
  4. 一度Injectorを作成した後は、go generateで再生成可能。
OsamuOsamu

Advanced Featuresとして、Binding InterfacesやStruct Providersなどがありますが、今回のスクラップの目的は達成したのでこれで完了とします。

このスクラップは4ヶ月前にクローズされました