🥊

【Go】GoでDI(Dependency Injection)をやってみた【google/wire】

2024/07/11に公開

Go言語を勉強し始めて、DIはどのようにやるのか気になったので、

実際に手を動かして試して行きたいと思います!

自分自身Go触りたてなので、まずは動くことを目的としてやっていきます!

使用したライブラリ

https://github.com/google/wire

wireを使用しました! Googleが提供しているので安心感がありますね。

tutorial
https://github.com/google/wire/blob/main/_tutorial/README.md

実装編

最終的なディレクトリ構造

.
├── Dockerfile
├── di
│   ├── wire.go
│   └── wire_gen.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── main.go
├── player
│   └── Player.go
└── shot
    └── Layup.go

実装方針

Player構造Layup構造に依存しているので、外側から依存性を注入することを目指します。

実装

ベースとなるコードの作成

まずはベースとなる2つのstructを定義、またそのstructを生成するfuncをそれぞれ作成します。
命名として、phpの癖でconstructorを含ませました(笑)。wireのドキュメントではprovideという名前が使われていました。

Player.go
package player

import (
	"go_di_sample/shot"
)

type Player struct {
	layup shot.Layup
}

func PlayerConstructor(layup shot.Layup) Player {
	return Player{layup}
}

func (p *Player)Shot(){
	p.layup.Shot()
}

Layup.go
package shot

import "fmt"

type Layup struct {
}

func LayupConstructor() Layup {
	return Layup{}
}

func (l *Layup) Shot(){
	fmt.Println("layup!!!")
}

DIの準備

プロジェクトフォルダ内の場所に、任意のファイル名でDI準備用のファイルを用意します。
wireはその準備用のファイルを使用して、新たなファイルを作成し、その中で依存性の解決を行なってくれます。

私はdi配下にwire.goを配置しました。

wire.go
//go:build wireinject
// +build wireinject

package di

import (
	"go_di_sample/player"
	"go_di_sample/shot"

	"github.com/google/wire"
)

type DiTarget struct {
	Player player.Player
	Layup shot.Layup
}


func InitialDi() (*DiTarget, error){
    wire.Build(player.PlayerConstructor, shot.LayupConstructor, wire.Struct(new(DiTarget), "*"))
	return nil, nil;
}

ファイル生成

以下のコマンドを実行。

wire gen di/wire.go

生成されたファイル

上記コマンドにより「wire_gen.go」ファイルが生成されました。

wire_gen.go
// Code generated by Wire. DO NOT EDIT.

//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package di

import (
	"github.com/google/wire"
	"go_di_sample/player"
	"go_di_sample/shot"
)

// Injectors from wire.go:

func InitialDi() (*DiTarget, error) {
	layup := shot.LayupConstructor()
	playerPlayer := player.PlayerConstructor(layup)
	diTarget := &DiTarget{
		Player: playerPlayer,
		Layup:  layup,
	}
	return diTarget, nil
}

// wire.go:

type DiTarget struct {
	Player player.Player
	Layup  shot.Layup
}

var SuperSet = wire.NewSet(player.PlayerConstructor, shot.LayupConstructor, wire.Struct(new(DiTarget), "*"))

wire.goでstruct生成funcをwire.Buildに指定することで、wire_gen.goの以下の部分で
外部からの注入を行なってくれました。

wire_gen.go
layup := shot.LayupConstructor()
playerPlayer := player.PlayerConstructor(layup)

またwire.go、wire_gen.go共にビルドタグを書いています。
つまりbuild実行時に、オプションを使用なければwire_gen.goがビルド対象となり、
wire.goは本番環境で必要なくなります。つまりwire.goでビルドタグの記述を書かないと、コンパイラーより

DiTarget redeclared in this block

と表示がされます。

main.goで使う

wire_gen.goで生成されたInitialDi funcを使ってみます。

main.go
package main

import (
	"go_di_sample/di"
)

func main() {
	diTarget,_ := di.InitialDi()
	diTarget.Player.Shot()
}

コマンドを実行し、正常に動作しました。

go run main.go


layup!!! ←実行結果

Discussion