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 타입을 받습니다. 여기서 타입이 일치하지 않는 문제가 발생합니다. 이를 해결하기 위해서
- provideMyFooer의 반환 타입을 Fooer로 변경합니다. (Go best practice는 concrete type을 반환하는 것이기 때문에 이 방법은 좋지 않습니다.)
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 함수를 반환하는 것을 확인할 수 있습니다.
Author And Source
이 문제에 관하여(Golang Wire), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jeonghyeon/Golang-Wire저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)