inject: go.pedge.io/inject Index | Files

package inject

import "go.pedge.io/inject"

Package inject is guice-inspired dependency injection for Go.

https://github.com/google/guice/wiki/Motivation

This project is in no way affiliated with the Guice project, but I recommend reading their docs to better understand the concepts.

Concepts

Module

A Module is analogous to Guice's AbstractModule, used for setting up your dependencies. This allows you to bind structs, struct pointers, interfaces, and primitives to singletons, constructors, with or without tags.

An interface can have a binding to another type, or to a singleton or constructor.

type SayHello interface {
	Hello() string
}

type SayHelloOne struct {
	value string
}

func (i *SayHelloOne) Hello() string {
	return i.value
}

module := inject.NewModule()
module.BindInterface((*SayHello)(nil)).To(&SayHelloOne{}) // valid, but must provide a binding to *SayHelloOne.
module.Bind(&SayHelloOne{}).ToSingleton(&SayHelloOne{"Salutations"}) // there we go

An interface can also be bound to a singleton or constructor.

module.Bind((*SayHello)(nil)).ToSingleton(&SayHelloOne{"Salutations"})

A struct, struct pointer, or primitive must have a direct binding to a singleton or constructor.

All errors from binding will be returned as one error when calling inject.NewInjector(...).

Injector

An Injector is analogous to Guice's Injector, providing your dependencies.

Given the binding:

module.Bind((*SayHello)(nil)).ToSingleton(&SayHelloOne{"Salutations"})

We are able to get a value for SayHello.

func printHello(aboveModule inject.Module) error {
	injector, err := inject.NewInjector(aboveModule)
	if err != nil {
		return err
	}
	sayHelloObj, err := injector.Get((*SayHello)(nil))
	if err != nil {
		return err
	}
	fmt.Println(sayHelloObj.(SayHello).Hello()) // will print "Salutations"
	return nil
}

See the Injector interface for other methods.

Constructor

A constructor is a function that takes injected values as parameters, and returns a value and an error.

type SayHelloToSomeone interface {
	Greetings() string
}

type SayHelloToSomeoneOne struct {
	sayHello SayHello
	person string
}

func (i *SayHelloToSomeoneOne) Greetings() string {
	return fmt.Sprintf("%s, %s!", i.sayHello.Hello(), i.person)
}

We can set up a constructor to take zero values, or values that require a binding in some module passed to NewInjector().

func doStuff() error {
	m1 := inject.NewModule()
	m1.Bind((*SayHello)(nil)).ToSingleton(&SayHelloOne{"Salutations"})
	m2 := inject.NewModule()
	m2.Bind((*SayHelloToSomeone)(nil)).ToConstructor(newSayHelloToSomeone)
	injector, err := inject.NewInjector(m1, m2)
	if err != nil {
		return err
	}
	sayHelloToSomeoneObj, err := injector.Get((*SayHelloToSomeone)(nil))
	if err != nil {
		return err
	}
	fmt.Println(sayHelloToSomeoneObj.(SayHelloToSomeone).Greetings()) // will print "Saluatations, Alice!"
	return nil
}

func newSayHelloToSomeone(sayHello SayHello) (SayHelloToSomeone, error) {
	return &SayHelloToSomeoneOne{sayHello, "Alice"}, nil
}

A singleton constructor will be called exactly once for the entire application.

var (
	unsafeCounter := 0
)

func doStuff() error {
	m1 := inject.NewModule()
	m1.Bind((*SayHello)(nil)).ToSingleton(&SayHelloOne{"Salutations"})
	m2 := inject.NewModule()
	m2.Bind((*SayHelloToSomeone)(nil)).ToSingletonConstructor(newSayHelloToSomeone)
	injector, err := inject.NewInjector(m1, m2)
	if err != nil {
		return err
	}
	sayHelloToSomeoneObj1, err := injector.Get((*SayHelloToSomeone)(nil))
	if err != nil {
		return err
	}
	fmt.Println(sayHelloToSomeoneObj1.(SayHelloToSomeone).Greetings()) // will print "Saluatations, Alice1!"
	sayHelloToSomeoneObj2, err := injector.Get((*SayHelloToSomeone)(nil))
	if err != nil {
		return err
	}
	fmt.Println(sayHelloToSomeoneObj2.(SayHelloToSomeone).Greetings()) // will print "Saluatations, Alice1!"
	return nil
}

func newSayHelloToSomeone(sayHello SayHello) (SayHelloToSomeone, error) {
	unsafeCounter++
	return &SayHelloToSomeoneOne{sayHello, fmt.Sprintf("Alice%d", unsafeCounter)}, nil
}

Functions be called from an injector using the Call function. These functions have the same parameter requirements as constructors, but can have any return types.

func doStuffWithAboveM1(m1 inject.Module) error {
	injector, err := inject.NewInjector(m1)
	if err != nil {
		return err
	}
	values, err := injector.Call(getStuff)
	if err != nil {
		return err
	}
	fmt.Println(values[0) // "Salutations"
	fmt.Println(values[1]) // 4
	return nil
}

func getStuff(sayHello SayHello) (string, int) {
	return sayHello.Hello(), 4
}

See the methods on Module and Constructor for more details.

Tags

A tag allows named multiple bindings of one type. As an example, let's consider if we want to have multiple ways to say hello.

func doStuff() error {
	module := inject.NewModule()
	module.BindTagged("english", (*SayHello)(nil)).ToSingleton(&SayHelloOne{"Hello"})
	module.BindTagged("german", (*SayHello)(nil)).ToSingleton(&SayHelloOne{"Guten Tag"})
	module.BindTagged("austrian", (*SayHello)(nil)).ToSingleton(&SayHelloOne{"Grüß Gott"})
	injector, err := inject.NewInjector(module)
	if err != nil {
		return err
	}
	_ = printHello("english", injector) // not error checking for the sake of shorter docs
	_ = printHello("german", injector)
	_ = printHello("austrian", injector)
	return nil
}

func printHello(tag string, injector inject.Injector) error {
	sayHelloObj, err := injector.GetTagged(tag)
	if err != nil {
		return err
	}
	fmt.Println(sayHelloObj.(SayHello).Hello())
	return nil
}

Structs can also be populated using the tag "inject".

type PopulateOne struct {
	// must be public
	English SayHello `inject:"english"`
	German SayHello `inject:"german"`
	Austrian SayHello `inject:"austrian"`
}

func printAllHellos(aboveInjector inject.Injector) error {
	populateOne := &PopulateOne{}
	if err := injector.Populate(populateOne); err != nil {
		return err
	}
	fmt.Println(populateOne.English.Hello())
	fmt.Println(populateOne.German.Hello())
	fmt.Println(populateOne.Austrian.Hello())
	return nil
}

Constructors can be tagged using structs, either named or anonymous.

type SayHowdy struct { // not interface, for this example
	value string
}

func(s *SayHowdy) Howdy() {
	return s.value
}

type PopulateTwo struct {
	PopulateOne *PopulateOne
	SayHowdy *SayHowdy
}

func doStuff(aboveModule inject.Module) error {
	module := inject.NewModule()
	module.Bind(&PopulateTwo).ToTaggedConstructor(newPopulateTwo)
	injector, err := inject.NewInjector(aboveModule, module)
	if err != nil {
		return err
	}
	populateTwo, err := injector.Get(&PopulateTwo{})
	if err != nil {
		return err
	}
	fmt.Printf("%+v\n", populateTwo)
	return nil
}

func newPopulateTwo(populateOne *PopulateOne) (*PopulateTwo, error) {
	return &PopulateTwo{
		PopulateOne, populateOne,
		SayHowdy: &SayHowdy{"howdy"},
	}, nil
}

// an anonymous struct can also be used in a constructor.
func newPopulateTwoAnonymous(str struct {
	// must be public
	English SayHello `inject:"english"`
	German SayHello `inject:"german"`
	Austrian SayHello `inject:"austrian"`
}) (*PopulateTwo, error) {
	return &PopulateTwo{
		PopulateOne: &PopulateOne{
			English: str.English,
			German: str.German,
			Austrian: str.Austrian,
		},
		SayHowdy: &SayHowdy{"howdy"},
	}, nil
}

A constructor can mix tagged values with untagged values in the input struct.

func doStuff(aboveModuleAgain injector.Module) error {
	aboveModuleAgain.Bind(&SayHowdy{}).ToSingleton(&SayHowdy{"howdy"})
	aboveModuleAgain.Bind(&PopulateTwo).ToTaggedConstructor(newPopulateTwo)
	injector, err := inject.NewInjector(aboveModuleAgain)
	if err != nil {
		return err
	}
	populateTwo, err := injector.Get(&PopulateTwo{})
	if err != nil {
		return err
	}
	fmt.Printf("%+v\n", populateTwo)
	return nil
}

func newPopulateTwo(str struct {
	English SayHello `inject:"english"`
	German SayHello `inject:"german"`
	Austrian SayHello `inject:"austrian"`
	SayHowdy SayHowdy
}) (*PopulateTwo, error) {
	return &PopulateTwo{
		PopulateOne: &PopulateOne{
			English: str.English,
			German: str.German,
			Austrian: str.Austrian,
		},
		SayHowdy: str.SayHowdy,
	}, nil
}

The CallTagged function works similarly to Call, except can take parameters like a tagged constructor.

Both Module and Injector implement fmt.Stringer for inspection, however this may be added to in the future to allow semantic inspection of bindings.

Index

Package Files

binding.go binding_key.go builder.go common.go constant_kind.go inject.go inject_error.go injector.go loader.go module.go

type Builder Uses

type Builder interface {
    ToSingleton(singleton interface{})
    ToConstructor(constructor interface{})
    ToSingletonConstructor(constructor interface{})
    ToTaggedConstructor(constructor interface{})
    ToTaggedSingletonConstructor(constructor interface{})
}

Builder is the return value from a Bind call from a Module.

type Injector Uses

type Injector interface {
    fmt.Stringer
    Get(from interface{}) (interface{}, error)
    GetTagged(tag string, from interface{}) (interface{}, error)
    GetTaggedBool(tag string) (bool, error)
    GetTaggedInt(tag string) (int, error)
    GetTaggedInt8(tag string) (int8, error)
    GetTaggedInt16(tag string) (int16, error)
    GetTaggedInt32(tag string) (int32, error)
    GetTaggedInt64(tag string) (int64, error)
    GetTaggedUint(tag string) (uint, error)
    GetTaggedUint8(tag string) (uint8, error)
    GetTaggedUint16(tag string) (uint16, error)
    GetTaggedUint32(tag string) (uint32, error)
    GetTaggedUint64(tag string) (uint64, error)
    GetTaggedFloat32(tag string) (float32, error)
    GetTaggedFloat64(tag string) (float64, error)
    GetTaggedComplex64(tag string) (complex64, error)
    GetTaggedComplex128(tag string) (complex128, error)
    GetTaggedString(tag string) (string, error)
    Call(function interface{}) ([]interface{}, error)
    CallTagged(taggedFunction interface{}) ([]interface{}, error)
    Populate(populateStruct interface{}) error
}

Injector provides your dependencies.

func NewInjector Uses

func NewInjector(modules ...Module) (Injector, error)

NewInjector creates a new Injector for the specified Modules.

Note that Modules are not thread-safe, it is your responsibility to make sure all Modules have all bindings in place before passing them as parameters to NewInjector.

type InterfaceBuilder Uses

type InterfaceBuilder interface {
    Builder
    To(to interface{})
}

InterfaceBuilder is the return value when binding an interface from a Module.

type Module Uses

type Module interface {
    fmt.Stringer
    Bind(from ...interface{}) Builder
    BindTagged(tag string, from ...interface{}) Builder
    BindInterface(fromInterface ...interface{}) InterfaceBuilder
    BindTaggedInterface(tag string, fromInterface ...interface{}) InterfaceBuilder
    BindTaggedBool(tag string) Builder
    BindTaggedInt(tag string) Builder
    BindTaggedInt8(tag string) Builder
    BindTaggedInt16(tag string) Builder
    BindTaggedInt32(tag string) Builder
    BindTaggedInt64(tag string) Builder
    BindTaggedUint(tag string) Builder
    BindTaggedUint8(tag string) Builder
    BindTaggedUint16(tag string) Builder
    BindTaggedUint32(tag string) Builder
    BindTaggedUint64(tag string) Builder
    BindTaggedFloat32(tag string) Builder
    BindTaggedFloat64(tag string) Builder
    BindTaggedComplex64(tag string) Builder
    BindTaggedComplex128(tag string) Builder
    BindTaggedString(tag string) Builder
}

Module sets up your dependencies.

Note that none of the calls to Module are thread-safe, it is your responsibility to make sure multiple goroutines are not calling a single module.

func NewModule Uses

func NewModule() Module

NewModule creates a new Module.

Package inject imports 6 packages (graph). Updated 2016-07-16. Refresh now. Tools for package owners.