Golang Wire

Basics

Defining Providers

Wire의 기본 동작은 provider입니다. provider는 value를 생성하는 go code입니다.

package foobarbaz

type Foo struct {
	X int
}

fucn ProvideFoo() Foo {
	return Foo{X: 42}
}

Provider는 파라미터를 통해 의존성을 표시할 수 있습니다.

package foobarbaz

// ...

type Bar struct {
	X int
}

func ProvideBar(foo Foo) Bar { // 인자로 Foo 타입을 받습니다.
	return Bar{X: -foo.X}
}

Provider는 error를 반환할 수 있습니다.

package foobarbaz

import (
	"context"
	"errors"
)

// ...

type Baz struct {
	X int
}

func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
	if bar.X == 0 {
		return Baz{}, errors.New("cannot provide baz when bar is zero")
	}
	return Baz{X: bar.X}, nil
}

Provider는 provider set으로 그룹지울 수 있습니다. 여러 provider가 같이 사용될 때 유용합니다. provider를 새로운 set에 추가하려면 wire.NewSet을 사용합니다.

package foobarbaz

import (
	// ...
	"github.com/google/wire"
)

// ...

var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)

기존 set에 새로운 provider를 추가할 수 있습니다.

var MegaSet = wire.NewSet(SuperSet, pkg.OtherSet)

Injectors

Injector는 provider를 의존성 순서에 따라 호출합니다.

Injector는 내부에서 wire.Build를 호출하는 함수입니다. 반환 값을 타입만 일치하면 문제가 되지 않습니다.

// +build wireinject

package main

import (
	"context"

	"github.com/google/wire"
	"example.com/foobarbaz"
)

func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
	wire.Build(foobarbaz.MegaSet)
	return foobarbaz.Baz{}, nil
}

injector가 아닌 코드는 생성된 파일에 복사됩니다.

이제 wire명령어를 통해 wire_gen.go파일을 생성합니다.

// Code generated by Wire. DO NOT EDIT.

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

package main

import (
    "example.com/foobarbaz"
)

func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    foo := foobarbaz.ProvideFoo()
    bar := foobarbaz.ProvideBar(foo)
    baz, err := foobarbaz.ProvideBaz(ctx, bar)
    if err != nil {
        return foobarbaz.Baz{}, err
    }
    return baz, nil
}

Advanced Features

Biding Interfaces

type Fooer interface {
	Foo() string
}

type MyFooer string

func (b *MyFooer) Foo() string {
	return string(*b)
}

func provideMyFooer() *MyFooer {
	b := new(MyFooer)
	*b = "Hello, World!"
	return b
}

type Bar string

func provideBar(f Fooer) string {
	return f.Foo()
}

var Set = wire.NewSet(
	provideMyFooer,
	wire.Bind(new(Fooer), new(*MyFooer)),
	provideBar)

예제에서 provideMyFooer는 *MyFooer 타입을 반환합니다. provideBar는 인자로 Fooer 타입을 받습니다. 여기서 타입이 일치하지 않는 문제가 발생합니다. 이를 해결하기 위해서

  1. provideMyFooer의 반환 타입을 Fooer로 변경합니다. (Go best practice는 concrete type을 반환하는 것이기 때문에 이 방법은 좋지 않습니다.)
  2. wire.Bind를 사용해 두 타입을 연결해줍니다. 첫 번째 인자는 provider가 반환하기를 기대하는 타입, 두 번째 인자는 provider가 실제로 반환하는 타입입니다.

Struct Providers

wire.Struct를 사용해서 struct 타입의 필드에 필요한 타입을 주입할 수 있습니다.

type Foo int
type Bar int

func ProvideFoo() Foo { /* ... */ }
func ProvideBar() Bar { /* ... */ }

type FooBar struct {
	MyFoo Foo
	MyBar Bar
}

var Set = wire.NewSet(
	ProvideFoo,
	ProvideBar,
	wire.Struct(new(FooBar), "MyFoo", "MyBar"))

wire.Struct의 첫 번째 인자는 원하는 struct의 포인터 입니다. 이어지는 인자는 주입할 struct의 필드 이름입니다. "*"를 사용하면 모든 필드를 주입할 수 있습니다. wire.Struct(new(FooBar), "*")

injector의 반환 타입에 따라 FooBar 또는 *FooBar를 반환할 수 있습니다.

// wire.go
func initializeFooBar() FooBar {
        wire.Build(Set)
        return FooBar{}
}

func initializeFooBarPointer() *FooBar {
        wire.Build(Set)
        return &FooBar{}
}
// wire_gen.go
func initializeFooBar() FooBar {
        foo := ProvideFoo()
        bar := ProvideBar()
        fooBar := FooBar{
                MyFoo: foo,
                MyBar: bar,
        }
        return fooBar
}

func initializeFooBarPointer() *FooBar {
        foo := ProvideFoo()
        bar := ProvideBar()
        fooBar := &FooBar{
                MyFoo: foo,
                MyBar: bar,
        }
        return fooBar
}

Struct가 sync.Mutex 필드를 가지고 있을 때 처럼 의존성 주입을 원하지 않을 경우도 있습니다. 이럴 경우 필드에 wire:"-" 태그를 적용해서 무시하도록 할 수 있습니다.

type Foo struct {
	mu sync.Mutex `wire:"-"`
	Bar bar
}

Binding Values

어떤 경우에는 특정한 값을 주입하고 싶을 때도 있습니다.

type Foo struct {
	X int
}

func injectFoo() Foo {
	wire.Build(wire.Value(Foo{X: 42}))
	return Foo{}
}

interface에 대해선느 InterfaceValue를 사용합니다.

func injectReader() io.Reader {
	wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))
	return nil
}

Use Fields of a Struct as Providers

종종 사용자가 원하는 provider가 Struct의 특정 필드인 경우가 있습니다. 이럴 경우 wire.FieldsOf를 사용해 특정 필드를 사용할 수 있습니다.

type Foo struct {
	S string
	N int
	F float64
}

func getS(foo Foo) string {
	// Foo.S 필드를 위한 Provider를 만드는 방법.
	// 좋지 않은 방법입니다. 대신 wire.FieldsOf를 사용하세요.
	return foo.S
}

func provideFoo() Foo {
	return Foo{ S: "Hello, World", N: 1, F: 3.14 }
}

func injectedMessageBad() string {
	wire.Build(
		provideFoo,
		getS)
	return ""
}

func injectedMessage() string {
	wire.Build(
		provideFoo,
		wire.FieldsOf(new(Foo), "S"))
	return ""
}

Cleanup functions

만약 provider가 cleanup이 필요한 리소스를 생성한다면, cleanup 함수를 반환할 수 있습니다. Injector는 모든 cleanup 함수를 모은 cleanup함수를 반환하거나, 에러 상황에는 cleanup함수를 호출합니다.

func provideFile(log Logger, path Path) (*os.File, func(), error) {
	f, err := os.Open(string(path))
	if err != nil {
		return nil, nil, err
	}
	cleanup := func() {
		if err := f.Close(); err != nil {
			log.Log(err)
		}
	}
	return f, cleanup, nil
}
// wire.go
func ProvideFoo() (Foo, func(), error){
        cleanup := func() {
                fmt.Println("ProvideFoo() cleanup")
        }
        return Foo(1), cleanup, nil
}

func ProvideBar() (Bar, func(), error){
        cleanup := func() {
                fmt.Println("ProvideBar() cleanup")
        }
        return Bar(2), cleanup, nil
}

var Set = wire.NewSet(
        ProvideFoo,
        ProvideBar,
        wire.Struct(new(FooBar), "MyFoo", "MyBar"))

func initializeFooBar() (FooBar, func(), error) {
        wire.Build(Set)
        return FooBar{}, func() {}, nil
}

// wire_gen.go
func initializeFooBar() (FooBar, func(), error) {
        foo, cleanup, err := ProvideFoo()
        if err != nil {
                return FooBar{}, nil, err
        }
        bar, cleanup2, err := ProvideBar()
        if err != nil {
                cleanup()
                return FooBar{}, nil, err
        }
        fooBar := FooBar{
                MyFoo: foo,
                MyBar: bar,
        }
        return fooBar, func() {
                cleanup2()
                cleanup()
        }, nil
}

생성된 injector를 보면 provider에서 반환한 cleanup 함수를 모두 호출하는 cleanup 함수를 반환하는 것을 확인할 수 있습니다.

좋은 웹페이지 즐겨찾기