# Wire

Provide autowire DI and static AOP proxy generate.

The DI core implement was based on wire (opens new window).

This plugin could make use of annotation context analysis, provide more intelligent inject types and inject provide cases, could be much more simply and easier.

# Usage

# Annotation

+zz:wire[:options...]

# Annotation Target

All objects.

# Optional Arguments

# bind

Bind annotated type with specify interface, if used on function type, bind with first return type.

Example:+zz:wire:bind=io.ReadCloser

# aop

If bind was used, generate interface invoke proxy for bind interface and replace binding.

In this case, plugin would generate wire_zzaop.go besides of wire_zset.go.

Example:+zz:wire:bind=io.ReadCloser:aop

Checkout Example-02 for detail.

# field

Use struct object fields as inject provide, and do not construct this struct value from fields anymore. This option is only use for struct.

Default: * to export all exported fields.

  • Use , to join multi fields.

Example: +zz:wire:field +zz:wire:field=User,Config

# inject

Specify this object as injectors target, it would generate injectors constructor functions and wire info (wire_zset.go) in specify filepath.

if filepath does not have suffix .go, use wire_zinject as default injectors filename.

Example: +zz:wire:inject=./

Function injectors follows format: func Initialize_T(...Params) (T, func(), error).

  • This option only works for type object.
  • If annotated type is struct, pointer type would return as : func Initialize_T(...Params) (*T, func(), error).
  • This option could be used in different objects and filepath, and files would generate in different filepath.

# param

If object has used option inject, specify type in param to be used as constructor params.

Example: +zz:wire:inject=./:param=context.Context

It would get constructor: func Initialize_T(context.Context) (T, func(), error).

# set

If set was specified, object would be collected into independent wire.NewSet named _${set}Set, or use default global set named _Set.

  • You could use ! prefix to do flexibly grouping, just like go build -tags.

Example:+zz:wire:set=!mock / +zz:wire:set=mock,unittest

# Other Convention

# Install wire

To ensure the DI core would be executed successful, please install wire:

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

# Constructor

If annotated type object file exist function named Provide{{ .Name }}, and this function have the same type or type pointer as first return, it would be used as inject provider.

Example:

package x

// +zz:wire
type Implement struct{}

func ProvideImplement() *Implement {
	return &Implement{}
}

# Referenced Type

If interface in bind option was came from other package, to make sure it was imported in annotated file. Otherwise, we could not know what exactly this package is.

The value specified in param is the same.

Example:

package x

import (
	"bytes"
)

// +zz:wire:bind=io.Closer
var Buff = &bytes.Buffer{}

In this case, we could not know what exactly io is.

Correct Usage:

package x

import (
	"bytes"
	"io"
)

var _ = (*io.Closer)(nil)

// +zz:wire:bind=io.Closer
var Buff = &bytes.Buffer{}

# Examples

# Example-01

Example Project (opens new window)

package wire01

//go:generate gozz run -p "wire" ./

// +zz:wire
type StructA struct{}

// +zz:wire
type StructB struct {
	InterfaceC
}

// +zz:wire:bind=InterfaceC
type StructC struct {
	StructA
}

func (StructC) Foo() {}

type InterfaceC interface {
	Foo()
}

// +zz:wire:inject=./
type Target struct {
	StructA
	StructB
	InterfaceC
}

In this example, we want to construct Target from provided types, and these types were dependent in some relation.

Execute gozz run -p "wire" ./.

It would generate files wire_gen.go wire_zinject.go wire_zset.go.

File wire_zset.go includes wire declarations that we should provide maintain in manually before. Now they were generated automatically.

// Code generated by gozz:wire github.com/go-zing/gozz. DO NOT EDIT.

//go:build wireinject
// +build wireinject

package wire01

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

var (
	_Set = wire.NewSet(
		// github.com/go-zing/gozz-doc-examples/wire01.StructA
		wire.Struct(new(StructA), "*"),

		// github.com/go-zing/gozz-doc-examples/wire01.StructB
		wire.Struct(new(StructB), "*"),

		// github.com/go-zing/gozz-doc-examples/wire01.StructC
		wire.Bind(new(InterfaceC), new(*StructC)),
		wire.Struct(new(StructC), "*"),

		// github.com/go-zing/gozz-doc-examples/wire01.Target
		wire.Struct(new(Target), "*"),
	)
)

File wire_zinject.go includes constructor and what set it's specified to use, that we should maintain in manually before. And they were generated automatically now.

// Code generated by gozz:wire github.com/go-zing/gozz. DO NOT EDIT.

//go:build wireinject
// +build wireinject

package wire01

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

// github.com/go-zing/gozz-doc-examples/wire01.Target
func Initialize_Target() (*Target, func(), error) {
	panic(wire.Build(_Set))
}

At last, we got wire_gen.go from wire, the DI constructor to provide target we want.

// Code generated by Wire. DO NOT EDIT.

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

package wire01

// Injectors from wire_zinject.go:

// github.com/go-zing/gozz-doc-examples/wire01.Target
func Initialize_Target() (*Target, func(), error) {
	structA := StructA{}
	structC := &StructC{
		StructA: structA,
	}
	structB := StructB{
		InterfaceC: structC,
	}
	target := &Target{
		StructA:    structA,
		StructB:    structB,
		InterfaceC: structC,
	}
	return target, func() {
	}, nil
}

# Example-02

Example Project (opens new window)

package wire02

import (
	"context"
)

//go:generate gozz run -p "wire" ./

// +zz:wire:bind=Interface
type Implement struct{}

// +zz:wire:bind=InterfaceX
// +zz:wire:bind=InterfaceX2:aop
type Interface interface {
	Foo(ctx context.Context, param int) (result int, err error)
	Bar(ctx context.Context, param int) (result int, err error)
}

type InterfaceX Interface
type InterfaceX2 Interface

// +zz:wire:inject=/
type Target struct {
	Interface
	InterfaceX
	InterfaceX2
}

func (Implement) Foo(ctx context.Context, param int) (result int, err error) {
	return
}

func (Implement) Bar(ctx context.Context, param int) (result int, err error) {
	return
}

In this example

  • We provided struct Implement to bind with Interface.
  • And Interface was bind with InterfaceX and InterfaceX2, but InterfaceX2 uses option aop.
  • The inject target was Target, depends on Interface,InterfaceX,InterfaceX2.

Execute gozz run -p "wire" ./, and focus on wire_gen.go.













 
 
 
 
 
 
 
 




// Code generated by Wire. DO NOT EDIT.

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

package wire02

// Injectors from wire_zinject.go:

// github.com/go-zing/gozz-doc-examples/wire02.Target
func Initialize_Target() (*Target, func(), error) {
	implement := &Implement{}
	wire02_impl_aop_InterfaceX2 := &_impl_aop_InterfaceX2{
		_aop_InterfaceX2: implement,
	}
	target := &Target{
		Interface:   implement,
		InterfaceX:  implement,
		InterfaceX2: wire02_impl_aop_InterfaceX2,
	}
	return target, func() {
	}, nil
}

We could find out, while implement Interface and InterfaceX, both of them use struct Implement, but InterfaceX2 with option aop added, was implement from impl_aop_InterfaceX2.

In wire_zset.go we could found different wire declaration of them.



















 
 
 
 






// Code generated by gozz:wire github.com/go-zing/gozz. DO NOT EDIT.

//go:build wireinject
// +build wireinject

package wire02

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

var (
	_Set = wire.NewSet(
		// github.com/go-zing/gozz-doc-examples/wire02.Implement
		wire.Bind(new(Interface), new(*Implement)),
		wire.Struct(new(Implement), "*"),

		// github.com/go-zing/gozz-doc-examples/wire02.Interface
		wire.Bind(new(InterfaceX), new(Interface)),
		wire.Bind(new(_aop_InterfaceX2), new(Interface)),
		wire.Struct(new(_impl_aop_InterfaceX2), "*"),
		wire.Bind(new(InterfaceX2), new(*_impl_aop_InterfaceX2)),

		// github.com/go-zing/gozz-doc-examples/wire02.Target
		wire.Struct(new(Target), "*"),
	)
)

Focus on the highlight lines.

Different from InterfaceX, InterfaceX2 was bind to _aop_InterfaceX2 than Interface.

And impl_aop_InterfaceX2 take that place of and bind to InterfaceX2.

We could found declaration of aop_InterfaceX2 and impl_aop_InterfaceX2 in wire_zzaop.go:















 
 






























// Code generated by gozz:wire github.com/go-zing/gozz. DO NOT EDIT.

package wire02

import (
	"context"
)

type _aop_interceptor interface {
	Intercept(v interface{}, name string, params, results []interface{}) (func(), bool)
}

// InterfaceX2
type (
	_aop_InterfaceX2      InterfaceX2
	_impl_aop_InterfaceX2 struct{ _aop_InterfaceX2 }
)

func (i _impl_aop_InterfaceX2) Foo(p0 context.Context, p1 int) (r0 int, r1 error) {
	if t, x := i._aop_InterfaceX2.(_aop_interceptor); x {
		if up, ok := t.Intercept(i._aop_InterfaceX2, "Foo",
			[]interface{}{&p0, &p1},
			[]interface{}{&r0, &r1},
		); up != nil {
			defer up()
		} else if !ok {
			return
		}
	}
	return i._aop_InterfaceX2.Foo(p0, p1)
}

func (i _impl_aop_InterfaceX2) Bar(p0 context.Context, p1 int) (r0 int, r1 error) {
	if t, x := i._aop_InterfaceX2.(_aop_interceptor); x {
		if up, ok := t.Intercept(i._aop_InterfaceX2, "Bar",
			[]interface{}{&p0, &p1},
			[]interface{}{&r0, &r1},
		); up != nil {
			defer up()
		} else if !ok {
			return
		}
	}
	return i._aop_InterfaceX2.Bar(p0, p1)
}

Struct _impl_aop_InterfaceX2 was made of Interface and implement _aop_InterfaceX2.

So that all methods call of Interface would be proxy intercepted by impl_aop_InterfaceX2.

And developers could do these awesome things through Intercept:

  • Inject some custom hooks before and after method execute.
  • Get caller and method name in these hooks.
  • Replace arguments and returns.
  • Cancel or skip method call.

# Some Useful Cases

  • Check returns error and save caller stack, do some logging, alerting, tracing and metrics.
  • Check auth status and permissions.
  • Do automatic cache with arguments and returns
  • Handle context.Context to check cancel or add timeout deadline.

# Example-03

Example Project (opens new window)

This Example show complex cases:

  • Inject values as providers.
  • Bind values as interfaces.
  • Build referenced struct type.
  • Inject provider function.
  • Inject struct fields as providers.
  • Group sets by option set.
  • Additional set from wire.NewSet
package wire03

import (
	"bytes"
	"database/sql"
	"io"

	"github.com/google/wire"
)

//go:generate gozz run -p "wire" ./

// provide value and interface value
// +zz:wire:bind=io.Writer:aop
// +zz:wire
var Buffer = &bytes.Buffer{}

// provide referenced type
// +zz:wire
type NullString nullString

type nullString sql.NullString

// use provider function to provide referenced type alias
// +zz:wire
type String = string

func ProvideString() String {
	return ""
}

// provide value from implicit type
// +zz:wire
var Bool = false

// +zz:wire:inject=/
type Target struct {
	Buffer     *bytes.Buffer
	Writer     io.Writer
	NullString NullString
	Int        int
}

// origin wire set
// +zz:wire
var Set = wire.NewSet(wire.Value(Int))

var Int = 0

// mock set injector
// +zz:wire:inject=/:set=mock
type mockString sql.NullString

// mock set string
// provide type from function
// +zz:wire:set=mock
func MockString() String {
	return "mock"
}

// mock set struct type provide fields
// +zz:wire:set=mock:field=*
type MockConfig struct{ Bool bool }

// mock set value
// +zz:wire:set=mock
var mock = &MockConfig{Bool: true}

Execute gozz run -p "wire" ./, and focus on wire_zset.go.

// Code generated by gozz:wire github.com/go-zing/gozz. DO NOT EDIT.

//go:build wireinject
// +build wireinject

package wire03

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

var (
	_Set = wire.NewSet(
		// github.com/go-zing/gozz-doc-examples/wire03.Buffer
		wire.InterfaceValue(new(_aop_io_Writer), Buffer),
		wire.Struct(new(_impl_aop_io_Writer), "*"),
		wire.Bind(new(io.Writer), new(*_impl_aop_io_Writer)),
		wire.Value(Buffer),

		// github.com/go-zing/gozz-doc-examples/wire03.NullString
		wire.Struct(new(NullString), "*"),

		// github.com/go-zing/gozz-doc-examples/wire03.String
		ProvideString,

		// github.com/go-zing/gozz-doc-examples/wire03.Bool
		wire.Value(Bool),

		// github.com/go-zing/gozz-doc-examples/wire03.Target
		wire.Struct(new(Target), "*"),

		// github.com/go-zing/gozz-doc-examples/wire03.Set
		Set,
	)

	_mockSet = wire.NewSet(
		// github.com/go-zing/gozz-doc-examples/wire03.mockString
		wire.Struct(new(mockString), "*"),

		// github.com/go-zing/gozz-doc-examples/wire03.MockString
		MockString,

		// github.com/go-zing/gozz-doc-examples/wire03.MockConfig
		wire.FieldsOf(new(*MockConfig), "Bool"),

		// github.com/go-zing/gozz-doc-examples/wire03.mock
		wire.Value(mock),
	)
)

We could satisfy most of the needs using +zz:wire and options bind inject only.

Last Updated: 10/27/2023, 12:58:51 PM