uber-go/digを触りました。

## はじめに
こんにちは。今回Media Do Advent Calendar 2日目を担当する新卒２年目のおかざわです。
社内で[ubar-go/dig](https://github.com/uber-go/dig)を勉強する機会があったので、その時のまとめになります。
参考文献が少なく公式ドキュメントを読み漁りながら作りました。
分かりやすさ重視なので内容はそこまでですが、暖かい目で見てやってください。

## 概要
> パッケージdigは、オブジェクトの依存関係を解決するための意見に基づいた方法を提供します。

[uber-go/digのdoc](https://godoc.org/go.uber.org/dig)のgoogle翻訳より

ぱっと見よくわからないですが、下記のコードの`func main()` のように依存関係を構築するコードが、uber-go/digを使うことで少し楽に書くことができるようになります。

```go
package main

import (
	"fmt"

	"go.uber.org/dig"
)

type Usecase interface {
	Use()
}

type Repository interface {
	RepoPrint()
}

type usecase struct {
	repo Repository
}

func NewUsecase(r Repository) Usecase {
	return &usecase{
		repo: r,
	}
}
func (u usecase) Use() {
	u.repo.RepoPrint()
}

type repository struct{}

func NewRepository() Repository {
	return &repository{}
}

func (r repository) RepoPrint() {
	fmt.Println("1")
}

func main() {
	// 通常の依存関係構築
	repo := NewRepository()
	usecase := NewUsecase(repo)
	usecase.Use()
}

```

## インストール
```
go get go.uber.org/dig@v1
```
go modulesをしている前提のインストールです。
それ以外は[ubar-go/dig](https://github.com/uber-go/dig)を参照ください。

## 使う
概要で軽く触れましたが例で挙げたコードのmainの部分（下記の部分）だけ変更します。
（importも勝手に追加されます）

```go
func main() {
	// 通常の依存関係構築
	repo := NewRepository()
	usecase := NewUsecase(repo)
	usecase.Use()
}
```

uber-go/digを使うと下記になります。

```go
func main() {
	c := dig.New()
	c.Provide(NewUsecase)
	c.Provide(NewRepository)
	c.Invoke(func(u Usecase) {
		u.Use()
	})
}
```

一つずつ見ていきます。

### コンテナ作成

```go
c := dig.New()
```
`dig.New()`でコンテナを生成します。
uber-go/digでは「コンテナ」と言う依存関係になる構造体を入れる用のインスタンスを作成し処理します。

### オブジェクト登録

```go
c.Provide(NewUsecase)
```

`Provide()`は先ほど作成したコンテナにオブジェクト(構造体)を登録します。
引数に指定しているものはファンクションで、上記のように既存のファンクションを指定するか、下記のようにファンクションをその場で作成する方法の２通りのの方法があります。

```go
c.Provide(func() Repository {
	return &repository{}
})
```

`Provide(func(引数)戻り値{処理})`で構築できます。

### オブジェクト構築/実行

```go
c.Invoke(func(usecase DogUsecase) {
	fmt.Println(usecase.All())
})
```

`Invoke()`は、コンテナに登録したオブジェクト達をよしなに解決して、指定したインターフェースの処理を実行することができます。
生成されるのは`Invoke()`内のみなので`Invoke()`の外で実行することはできません。

以上が例題をuber-go/digで処理してみた結果です。

## 美味しい？

uber-go/digで依存関係を構築することができることがわかりました。
ですが、上記の例ではあまりuber-go/digを使う美味しさがわからなかったと思います。

もう少しuber-go/digについて掘り下げて行きます。

## dig.In

下記の例を使って説明します。

<details>
    <summary>最低限機能しか使っていないuber-go/digの例</summary><div>
    
```go
package main

import (
	"fmt"

	"go.uber.org/dig"
)

type Usecase interface {
	use()
}

type FooRepository interface {
	foo()
}

type BarRepository interface {
	bar()
}

type usecase struct {
	Foo FooRepository
	Bar BarRepository
}

func (u *usecase) use() {
	u.Foo.foo()
	u.Bar.bar()
}

type fooRepo struct {
	f string
}

func (u *fooRepo) foo() { fmt.Println(u.f) }

type barRepo struct {
	b string
}

func (u *barRepo) bar() { fmt.Println(u.b) }

func main() {

	c := dig.New()
	c.Provide(func() FooRepository { return &fooRepo{f: "Foo"} })
	c.Provide(func() BarRepository { return &barRepo{b: "Bar"} })
	c.Provide(func(f FooRepository, b BarRepository) Usecase {
		return &usecase{
			Foo: f,
			Bar: b,
		}
	})
	c.Invoke(func(u Usecase) {
		u.use()
	})
}

```

</div></details>

`Invoke(func(u Usecase))`を実行すると`usecase`が生まれ、`usecase`に定義されている
`FooRepository`と`BarRepository`を満たす`fooRepo`と`barRepo`も自動的に生成/関連付けされます。

下記部分はusecaseの生成方法の登録を行っています。

```go
c.Provide(func(f FooRepository, b BarRepository) Usecase {
	return &usecase{
		Foo: f,
		Bar: b,
	}
})
```
例では`FooRepository`と`BarRepository`の2つしか引数に取っていないが、実装によっては何個も定義を書く必要があり、可読性を大きく損なう可能性があります。（引数4つ以上あると、もう見辛い）
uber-go/digではこの解決方法として「パラメータオブジェクト」を使用します。

> このような状況で読みやすくするために使用されるパターンは、関数のすべてのパラメーターをフィールドとしてリストする構造体を作成し、代わりにその構造体を受け入れるように関数を変更することです。これは、パラメータオブジェクトと呼ばれます。

[uber-go/digのdoc](https://godoc.org/go.uber.org/dig)のgoogle翻訳より

上記の通り、パラメータ（引数とか）をフィールドとした構造体を定義し、関数にはその構造体を受け入れるように変更を加えることで可読性の低下を阻止します。
上記の例の中に下記の構造体を追加し、関数を下記の構造体を受け入れるように変更します。

```go
type inUsecase struct {
	dig.In
	Foo FooRepository
	Bar BarRepository
}
```

```go
c.Provide(func(u inUsecase) Usecase {
	return &usecase{
		Foo: u.Foo,
		Bar: u.Bar,
	}
})
```

`dig.In`を宣言することで「パラメータオブジェクト」になります。
パラメータが少ない場合は無理に`dig.In`する必要は無いですが、複数パラメータを宣言しなければいけない時は便利だと思います。

<details>
    <summary>`dig.In`を追加した場合の全体像</summary><div>

```go
package main

import (
	"fmt"

	"go.uber.org/dig"
)

type Usecase interface {
	use()
}

type FooRepository interface {
	foo()
}

type BarRepository interface {
	bar()
}

// dig.Inが定義されているオブジェクト
type inUsecase struct {
	dig.In
	Foo FooRepository
	Bar BarRepository
}

type usecase struct {
	Foo FooRepository
	Bar BarRepository
}

func (u *usecase) use() {
	u.Foo.foo()
	u.Bar.bar()
}

type fooRepo struct {
	f string
}

func (u *fooRepo) foo() { fmt.Println(u.f) }

type barRepo struct {
	b string
}

func (u *barRepo) bar() { fmt.Println(u.b) }

func main() {

	// dig.Inを使った場合
	c := dig.New()
	c.Provide(func() FooRepository { return &fooRepo{f: "Foo"} })
	c.Provide(func() BarRepository { return &barRepo{b: "Bar"} })
	// dig.Inが定義されている構造体を指定することにより可読性の低下を抑える
	c.Provide(func(u inUsecase) Usecase {
		return &usecase{
			Foo: u.Foo,
			Bar: u.Bar,
		}
	})
	c.Invoke(func(u Usecase) {
		u.use()
	})
}
```

</div></details>

## nameオプション
uber-go/digは依存関係を「よしな」にしてくれるすごいライブラリです。
ですが、下記の例の様に`dig.In`に重複する型がある場合「よしな」が発動しません。

<details>
    <summary>「よしな」にならない例</summary><div>
    
```go

package main

import (
	"fmt"

	"go.uber.org/dig"
)

type Usecase interface {
	use()
}

type FooBarRepository interface {
	foobar()
}

type inUsecase struct {
	dig.In
	FB1 FooBarRepository
	FB2 FooBarRepository
}

type usecase struct {
	FB1 FooBarRepository
	FB2 FooBarRepository
}

func (u *usecase) use() {
	u.FB1.foobar()
	u.FB2.foobar()
}

type foobarRepo struct {
	fb string
}

func (u *foobarRepo) foobar() { fmt.Println(u.fb) }

func main() {

	c := dig.New()
	c.Provide(func() FooBarRepository { return &foobarRepo{fb: "Foo"} })
	c.Provide(func() FooBarRepository { return &foobarRepo{fb: "Bar"} })
	// inUsecaseは上記2つのFooBarRepositoryの内、先に宣言された"Foo"のみを取得する
	c.Provide(func(u inUsecase) Usecase {
		return &usecase{
			FB1: u.FB1, // Foo
			FB2: u.FB2, // Foo
		}
	})
	c.Invoke(func(u Usecase) {
		u.use()
	})
}

```

</div></details>

コード内にも書いていますが、先に宣言された「`Foo`」しか表示されません。
このように同じ型を宣言してしまった場合の回避方法として`name`オプションを使用することができます。
パラメータに対して`name`を宣言することにより、登録時に同じ`name`のオブジェクトが関連付けされるようになります。

```go
type inUsecase struct {
	dig.In
	FB1 FooBarRepository `name:"Foo"`
	FB2 FooBarRepository `name:"Bar"`
}
```

`dig.Name()`を登録時に第二引数で指定することにより、オブジェクトに`name`が付きます。

```go
c.Provide(func() FooBarRepository { return &foobarRepo{fb: "Foo"} }, dig.Name("Foo"))
c.Provide(func() FooBarRepository { return &foobarRepo{fb: "Bar"} }, dig.Name("Bar"))
```

実行すると
`Foo`
`Bar`
と表示され、期待通りの関連付けができます。

<details>
    <summary> `name`オプションを追加した場合の全体像 </summary><div>
    
```go
package main

import (
	"fmt"

	"go.uber.org/dig"
)

type Usecase interface {
	use()
}

type FooBarRepository interface {
	foobar()
}

type inUsecase struct {
	dig.In
	FB1 FooBarRepository `name:"Foo"`
	FB2 FooBarRepository `name:"Bar"`
}

type usecase struct {
	FB1 FooBarRepository
	FB2 FooBarRepository
}

func (u *usecase) use() {
	u.FB1.foobar()
	u.FB2.foobar()
}

type foobarRepo struct {
	fb string
}

func (u *foobarRepo) foobar() { fmt.Println(u.fb) }

func main() {

	c := dig.New()
	c.Provide(func() FooBarRepository { return &foobarRepo{fb: "Foo"} }, dig.Name("Foo"))
	c.Provide(func() FooBarRepository { return &foobarRepo{fb: "Bar"} }, dig.Name("Bar"))
	c.Provide(func(u inUsecase) Usecase {
		return &usecase{
			FB1: u.FB1,
			FB2: u.FB2,
		}
	})
	c.Invoke(func(u Usecase) {
		u.use()
	})
}
```

</div></details>

## dig.Out
goは複数戻り値を得ることができる言語です。
`Provide`でオブジェクトを登録するときも一つの`Provide`内で複数のオブジェクトを登録することができます。
あまり使用する機会は無いかもしれませんが、沢山オブジェクトを生成するコード = 戻り値が沢山あるコードとなり、可読性を損ないます。（引数と同じで戻り値も沢山あると見辛い）
戻り値もパラメータオブジェクト化することができます。

 上記にある「**nameオプションを追加した場合の全体像**」を例として使います。 

例に下記の構造体を追加し、`FooBarRepository`を登録している部分をパラメータオブジェクトを受け入れる様に変更します。

```go
type outUsecase struct {
	dig.Out
	FB1 FooBarRepository `name:"Foo"`
	FB2 FooBarRepository `name:"Bar"`
}
```

```go
c.Provide(func() outUsecase {
	return outUsecase{
		FB1: &foobarRepo{fb: "Foo"},
		FB2: &foobarRepo{fb: "Bar"},
	}
})
```

`dig.Out`を使うことにより複数オブジェクトを登録することができたり、`name`オプションの宣言を`dig.Out`内で行うことができます。
`name`オプションなどをmain内に書かなくてよくなるのは良いと思いました。

<details>
    <summary>dig.Outを追加した場合の全体像</summary><div>
    
```go
package main

import (
	"fmt"

	"go.uber.org/dig"
)

type Usecase interface {
	use()
}

type FooBarRepository interface {
	foobar()
}

type inUsecase struct {
	dig.In
	FB1 FooBarRepository `name:"Foo"`
	FB2 FooBarRepository `name:"Bar"`
}

// dig.Inの反対でdig.Outはオブジェクトの出力の形式を宣言する
// dig.Outに`name:`を宣言することでProvide()の中で宣言しなくてよくなる
type outUsecase struct {
	dig.Out
	FB1 FooBarRepository `name:"Foo"`
	FB2 FooBarRepository `name:"Bar"`
}

type usecase struct {
	FB1 FooBarRepository
	FB2 FooBarRepository
}

func (u *usecase) use() {
	u.FB1.foobar()
	u.FB2.foobar()
}

type foobarRepo struct {
	fb string
}

func (u *foobarRepo) foobar() { fmt.Println(u.fb) }

func main() {

	// dig.Outを使った場合
	c := dig.New()
	// dig.Outを使うことにより複数オブジェクトを登録することができたり
	// nameオプションの宣言をdig.Out内で行うことができる
	c.Provide(func() outUsecase {
		return outUsecase{
			FB1: &foobarRepo{fb: "Foo"},
			FB2: &foobarRepo{fb: "Bar"},
		}
	})
	c.Provide(func(u inUsecase) Usecase {
		return &usecase{
			FB1: u.FB1,
			FB2: u.FB2,
		}
	})
	c.Invoke(func(u Usecase) {
		u.use()
	})
}

```

</div></details>

## optionalオプション
uber-goにはその構造体が「あっても無くてもOK」を実装することができます。
それが`optional`オプションです。
`optional`オプションがついているパラメータは依存関係構築(`Invoke`)時に
対象となるオブジェクトがあれば使い、
対象となるオブジェクトがなければ「ゼロ値」を生成します。

<details>
    <summary>`optional`オプションの実装</summary><div>
    
```go
package main

import (
	"fmt"

	"go.uber.org/dig"
)

type Usecase interface {
	use()
}

type FooBarRepository interface {
	foobar()
}

type inUsecase1 struct {
	dig.In
	FB1 FooBarRepository `name:"Foo"`
	FB2 FooBarRepository `name:"Bar"`
}

// dig.Inに`optional:"true"`を宣言することにより
// 取得できなかった時にその型のゼロ値を取得する
type inUsecase2 struct {
	dig.In
	FB1 FooBarRepository `name:"Foo" optional:"true"`
	FB2 FooBarRepository `name:"Bar" optional:"true"`
}

// 今回は使用しない
type outUsecase struct {
	dig.Out
	FB1 FooBarRepository `name:"Foo"`
	FB2 FooBarRepository `name:"Bar"`
}

type usecase struct {
	FB1 FooBarRepository
	FB2 FooBarRepository
}

func (u *usecase) use() {
	// optional:"true"が宣言されており、登録オブジェクトが無い場合はゼロ値で処理される
	if u.FB1 != nil {
		u.FB1.foobar()
	} else {
		fmt.Println("usecase FB1 is nil")
	}

	if u.FB2 != nil {
		u.FB2.foobar()
	} else {
		fmt.Println("usecase FB2 is nil")
	}
}

type foobarRepo struct {
	fb string
}

func (u *foobarRepo) foobar() { fmt.Println(u.fb) }

func main() {

	// optionalを用いることで「あっても無くても良いオブジェクト」を宣言することができる
	// optional:"true"が宣言されていなかった場合
	c1 := dig.New()
	// オブジェクトを登録しない
	c1.Provide(func(u inUsecase1) Usecase {
		return &usecase{
			FB1: u.FB1,
			FB2: u.FB2,
		}
	})
	// 実行時にエラーがでる
	err1 := c1.Invoke(func(u Usecase) {
		u.use()
	})
	if err1 != nil {
		fmt.Printf("Invoke error :\n%s\n\n", err1)
	}

	// optional:"true"が宣言されていた場合
	c2 := dig.New()
	// オブジェクトを登録しない
	c2.Provide(func(u inUsecase2) Usecase {
		return &usecase{
			FB1: u.FB1,
			FB2: u.FB2,
		}
	})
	// ゼロ値が登録されて実行される（構造体ならnil）
	err2 := c2.Invoke(func(u Usecase) {
		u.use()
	})
	if err2 != nil {
		fmt.Printf("Invoke error :\n%s", err2)
	}
}

```

</div></details>

`optional:"true"`を宣言していない方(`inUsecase1`)は依存関係構築時にエラーを吐きます。
`optional:"true"`を宣言している方(`inUsecase2`)は依存関係構築時にゼロ値のオブジェクトを生成し、`u.use()`を実行しています。（構造体のゼロ値はnil）
あらかじめ`optional`のパラメータを受け取る関数内では、ゼロ値かどうかの判定を作る必要があります。

むやみやたらに`optional`を使うとバグの原因になりそうですが、「あっても無くてもOK」を簡単に実装できるのは（あまり使わないと思いますが）便利です。

## groupオプション
`group`オプションはuber-go/digのバージョン1.2から追加された機能です。
`dig.In`と`dig.Out`両方に宣言する必要があるオプションで、`dig.Out`されたオブジェクトを`dig.In`にスライスで渡します。（複数オブジェクトを渡すことができる）

<details>
    <summary>`group`オプションの実装</summary><div>
    
```go
package main

import (
	"fmt"

	"go.uber.org/dig"
)

type Usecase interface {
	use()
}

type FooBarRepository interface {
	foobar()
}

// dig.Inのgroupオプションの宣言
type inUsecase struct {
	dig.In
	FB []FooBarRepository `group:"foobar"`
}

// dig.Outのgroupオプションの宣言
type outUsecase struct {
	dig.Out
	FB FooBarRepository `group:"foobar"`
}

// dig.Inから受け取る構造体もスライスにする必要がある
type usecase struct {
	FB []FooBarRepository
}

// 値の取り出しはfor文で取り出すことができる
// ただし取り出す順番をdigは保証しないらしい
// 実際実行するたびに順番が変わる
func (u *usecase) use() {
	for _, fb := range u.FB {
		fb.foobar()
	}
}

type foobarRepo struct {
	fb string
}

func (u *foobarRepo) foobar() { fmt.Println(u.fb) }

func main() {

	// groupオプションがついているdig.Outを使用すると
	// dig.Inについているgroupのスライスで受け取ることができる
	// ただし、digライブラリはスライスに登録される順番を保証しない
	c := dig.New()
	c.Provide(func() outUsecase { return outUsecase{FB: &foobarRepo{fb: "Foo"}} })
	c.Provide(func() outUsecase { return outUsecase{FB: &foobarRepo{fb: "Foo"}} })
	c.Provide(func() outUsecase { return outUsecase{FB: &foobarRepo{fb: "Bar"}} })
	c.Provide(func() outUsecase { return outUsecase{FB: &foobarRepo{fb: "Bar"}} })
	c.Provide(func(u inUsecase) Usecase {
		return &usecase{
			FB: u.FB,
		}
	})
	c.Invoke(func(u Usecase) {
		u.use()
	})
}
```

</div></details>

`dig.Out`に`group`オプションを作成し、`dig.In`には`dig.Out`と同じ名前の`group`オプションをスライス型で定義します。
`dig.In`を受け取るオブジェクト(usecase)もスライスで定義し、実行する時にfor文などでオブジェクトを取得します。
コード内にも書いていますが、digは`group`オプションで登録される順番を保証しません。
このプログラムを何度も動かしたらわかりますが、実行するたびに「Foo」「Bar」の順番が変わります。

同じ型のオブジェクトをゴルーチンを用いて並列処理する時とかに使うと便利そうだ思いました。

## その他

 **dig.IsIn()** で対象の構造体が`dig.In`かどうか判定できる。

 **dig.IsOut()** で対象の構造体が`dig.Out`かどうか判定できる。

 **コンテナ.String()**で **コンテナに登録されている情報を確認する** ことができる 。
↑結構重要

 **コンテナ.Provide(),コンテナ.Invoke()** は戻り値で **エラー** をとることができる。
↑結構重要


## 感想
uber-go/digを使うことにより依存関係の構築を簡単にすることができる。
ただ、ドキュメントが少なかったし、ポインタ周りでかなりハマったりしたので疲れた。
良い勉強になったと思う。
あとマークダウンを書くときに

<details>
    <summary>このパカパカ</summary><div>
    
パカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカ

```html
<details>
    <summary>開閉オブジェクト</summary>
    <div>
        中身
    </div>
</details>
```
パカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカパカ

</div></details>

が便利だと思った。

以上。

### 参考文献

doc :https://godoc.org/go.uber.org/dig
github :https://github.com/uber-go/dig
https://int128.hatenablog.com/entry/2019/01/28/143158
https://tech.raksul.com/2019/06/21/golang-favorite-di-tool/