inject

package module
v0.0.0-...-a9441b7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 8, 2018 License: MIT Imports: 5 Imported by: 5

README

Inject - Guice-ish dependency-injection for Go.

Build Status Gitter chat

Inject provides dependency injection for Go. For small Go applications, manually constructing all required objects is more than sufficient. But for large, modular code bases, dependency injection can alleviate a lot of boilerplate.

In particular, Inject provides a simple way of wiring together modular applications. Each module contains configuration and logic to create the objects it provides. The main application installs all of these modules, then calls its main entry point using the injector. The injector resolves any dependencies of the main function and injects them.

Example usage

The following example illustrates a simple modular application.

First, the main package installs configured modules and calls an entry point:

package main

func run(db *mgo.Database, log *log.Logger) {
  log.Println("starting application")
  // ...
}

func main() {
  injector := New()
  injector.Install(
    &MongoModule{URI: "mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&connectTimeoutMS=300000"""},
    &LoggingModule{Flags: log.Ldate | log.Ltime | log.Llongfile},
  )
  injector.Call(run)
}

Next we have a simple Mongo module with a configurable URI:

package db

type MongoModule struct {
  URI string
}

func (m *MongoModule) ProvideMongoDB() (*mgo.Database, error) {
  return mgo.Dial(m.URI)
}

The logging package shows idiomatic use of inject; it is just a thin wrapper around normal Go constructors. This is the least invasive way of using injection, and preferred.

package logging

// LoggingModule provides a *log.Logger that writes log lines to a Mongo collection.
type LoggingModule struct {
  Flags int
}

func (l *LoggingModule) ProvideMongoLogger(db *mgo.Database) *log.Logger {
  return NewMongoLogger(db, l.Flags)
}

type logEntry struct {
  Text string `bson:"text"`
}

func NewMongoLogger(db *mgo.Database, flags int) *log.Logger {
  return log.New(&mongologWriter{c: db.C("logs")}, "", flags)
}

type mongoLogWriter struct {
  buf string
  c *mgo.Collection
}

func (m *mongoLogWriter) Write(b []byte) (int, error) {
  m.buf = m.buf + string(b)
  for {
    eol := strings.Index(m.buf, "\n")
    if eol == -1 {
      return len(b), nil
    }
    line := m.buf[:eol]
    err := m.c.Insert(&logEntry{line})
    if err != nil {
      return len(b), err
    }
    m.buf = m.buf[eol:]
  }
}

Value bindings

The simplest form of binding simply binds a value directly:

injector.Bind(http.DefaultServeMux)

Singletons

Function bindings are not singleton by default. For example, the following function binding will be called each time an int is requested:

value := 0
injector.Bind(func() int {
  value++
  return value
})

Wrap the function in Singleton() to ensure it is called only once:

injector.Bind(Singleton(func() int {
  return 10
}))

Literals

To bind a function as a value, use Literal:

injector.Bind(Literal(fmt.Sprintf))

Mapping bindings

Mappings can be bound explicitly:

injector.Bind(Mapping(map[string]int{"one": 1}))
injector.Bind(Mapping(map[string]int{"two": 2}))
injector.Bind(Mapping(func() map[string]int { return map[string]int{"three": 3} }))
injector.Call(func(m map[string]int) {
  // m == map[string]int{"one": 1, "two": 2, "three": 3}
})

Or provided via a Provider method that includes the term Mapping in its name:

func (m *MyModule) ProvideStringIntMapping() map[string]int {
  return map[string]int{"one": 1, "two": 2}
}

Sequence bindings

Sequences can be bound explicitly:

injector.Bind(Sequence([]int{1, 2}))
injector.Bind(Sequence([]int{3, 4}))
injector.Bind(Sequence(func() []int { return  []int{5, 6} }))
injector.Call(func(s []int) {
  // s = []int{1, 2, 3, 4, 5, 6}
})

Or provided via a Provider method that includes the term Sequence in its name:

func (m *MyModule) ProvideIntSequence() []int {
  return []int{1, 2}
}

Named bindings

The equivalent of "named" values can be achieved with type aliases:

type UserName string

injector.Bind(UserName("Bob"))
injector.Call(func (username UserName) {})

Interfaces

Interfaces can be explicitly bound to implementations:

type stringer string
func (s stringer) String() string { return string(s) }

injector.BindTo((*fmt.Stringer)(nil), stringer("hello"))
injector.Call(func(s fmt.Stringer) {
  fmt.Println(s.String())
})

However, if an explicit interface binding is not present, any bound object implementing that interface will be used:

injector.Bind(stringer("hello"))
injector.Call(func(s fmt.Stringer) {
  fmt.Println(s.String())
})

Similarly, if sequences/maps of interfaces are injected, explicit bindings will be used first, then inject will fallback to sequences/maps of objects implementing that interface.

Modules

Similar to injection frameworks in other languages, inject includes the concept of modules. A module is a struct whose methods are providers. This is useful for grouping configuration data together with providers.

Any method starting with "Provide" will be bound as a Provider. If the method name contains "Multi" it will not be a singleton provider. If the method name contains "Sequence" it will contribute to a sequence of its return type. Similarly, if the method name contains "Mapping" it will contribute to a mapping of its return type.

type MyModule struct {}

// Singleton provider.
func (m *MyModule) ProvideLog() *log.Logger { return log.New() }

type Randomness int

// "Multi" provider, called every time an "int" is injected.
func (m *MyModule) ProvideMultiRandomness() Randomness { return Randomness(rand.Int()) }

Validation

Finally, after binding all of your types to the injector you can validate that a function is constructible via the Injector by calling Validate(f).

Or you can live on the edge and simply use Call(f) which will panic if injection is not possible.

Documentation

Overview

Package inject provides dependency injection for Go. For small Go applications, manually constructing all required objects is more than sufficient. But for large, modular code bases, dependency injection can alleviate a lot of boilerplate.

The following example illustrates a simple modular application.

First, the main package installs configured modules and calls an entry point:

package main

func run(db *mgo.Database, log *log.Logger) {
  log.Println("starting application")
  // ...
}

func main() {
  injector := New()
  injector.Install(
    &MongoModule{URI: "mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&connectTimeoutMS=300000"""},
    &LoggingModule{Flags: log.Ldate | log.Ltime | log.Llongfile},
  )
  injector.Call(run)
}

Next we have a simple Mongo module with a configurable URI:

package db

type MongoModule struct {
  URI string
}

func (m *MongoModule) ProvideMongoDB() (*mgo.Database, error) {
  return mgo.Dial(m.URI)
}

The logging package shows idiomatic use of inject; it is just a thin wrapper around normal Go constructors. This is the least invasive way of using injection, and preferred.

package logging

// LoggingModule provides a *log.Logger that writes log lines to a Mongo collection.
type LoggingModule struct {
  Flags int
}

func (l *LoggingModule) ProvideMongoLogger(db *mgo.Database) *log.Logger {
  return NewMongoLogger(db, l.Flags)
}

type logEntry struct {
  Text string `bson:"text"`
}

func NewMongoLogger(db *mgo.Database, flags int) *log.Logger {
  return log.New(&mongologWriter{c: db.C("logs")}, "", flags)
}

type mongoLogWriter struct {
  buf string
  c *mgo.Collection
}

func (m *mongoLogWriter) Write(b []byte) (int, error) {
  m.buf = m.buf + string(b)
  for {
    eol := strings.Index(m.buf, "\n")
    if eol == -1 {
      return len(b), nil
    }
    line := m.buf[:eol]
    err := m.c.Insert(&logEntry{line})
    if err != nil {
      return len(b), err
    }
    m.buf = m.buf[eol:]
  }
}

Two interfaces to the injector are provided: SafeInjector and Injector. The former will return error values and the latter will panic on any error. The latter is commonly used because DI failures are typically not user-recoverable.

See the [README](https://github.com/alecthomas/inject/blob/master/README.md) for more details.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Annotation

type Annotation interface {
	// Build returns the type associated with the value being bound, and a function that builds that
	// value at runtime.
	Build(*SafeInjector) (*Binding, error)
	// Is checks if the annotation or any children are of the given annotation type.
	Is(annotation Annotation) bool
}

An Annotation modifies how a type is built and retrieved from the SafeInjector.

func Annotate

func Annotate(v interface{}) Annotation

Annotate ensures that v is an annotation, and returns it.

Specifically:

- If v is already an Annotation it will be returned as-is. - If v is a function it will be converted to a Provider(). - Any other value will become a Literal().

func Literal

func Literal(v interface{}) Annotation

Literal annotates a value as being provided as-is with no further transformation.

func Mapping

func Mapping(v interface{}) Annotation

Mapping annotates a provider or binding to indicate it is part of a mapping of keys to values.

injector.Bind(Mapping(map[string]int{"one": 1}))
injector.Bind(Mapping(map[string]int{"two": 2}))
injector.Provide(Mapping(func() map[string]int { return map[string]int{"three": 3} }))

expected := map[string]int{"one": 1, "two": 2, "three": 3}
actual := injector.Get(reflect.TypeOf(map[string]int{}))
assert.Equal(t, actual, expected)

func Provider

func Provider(v interface{}) Annotation

Provider annotates a function to indicate it should be called whenever the type of its return value is requested.

func Sequence

func Sequence(v interface{}) Annotation

Sequence annotates a provider or binding to indicate it is part of a slice of values implementing the given type.

injector.Bind(Sequence([]int{1}))
injector.Bind(Sequence([]int{2}))

expected := []int{1, 2}
actual := injector.Get(reflect.TypeOf([]int{}))
assert.Equal(t, actual, expected)

func Singleton

func Singleton(v interface{}) Annotation

Singleton annotates a provider function to indicate that the provider will only be called once, and that its return value will be used for all subsequent retrievals of the given type.

count := 0
injector.Bind(Singleton(func() int {
	count++
	return 123
}))
injector.Get(reflect.TypeOf(1))
injector.Get(reflect.TypeOf(1))
assert.Equal(t, 1, count)

type Binder

type Binder interface {
	Bind(things ...interface{}) Binder
	BindTo(to interface{}, impl interface{}) Binder
	Install(module ...interface{}) Binder
}

Binder is an interface allowing bindings to be added.

type Binding

type Binding struct {
	Provides reflect.Type
	Requires []reflect.Type
	Build    func() (interface{}, error)
}

Binding represents a function that resolves to a value given a set of input values.

type Injector

type Injector struct {
	// contains filtered or unexported fields
}

SafeInjector is an IoC container.

func New

func New() *Injector

New creates a new Injector.

An unsafe injector panics on any error. This is commonly used because DI failures are generally not user-recoverable.

The injector itself is already bound, as is an implementation of the Binder interface.

func (*Injector) Bind

func (i *Injector) Bind(things ...interface{}) Binder

Bind binds a value to the injector. Panics on error. See the README (https://github.com/alecthomas/inject/blob/master/README.md) for more details.

func (*Injector) BindTo

func (i *Injector) BindTo(iface interface{}, impl interface{}) Binder

BindTo binds an interface to a value. Panics on error.

"as" should either be a nil pointer to the required interface:

i.BindTo((*fmt.Stringer)(nil), impl)

Or a type to convert to:

i.BindTo(int64(0), 10)

func (*Injector) Call

func (i *Injector) Call(f interface{}) []interface{}

Call calls f, injecting any arguments, and panics if the function errors.

func (*Injector) Child

func (i *Injector) Child() *Injector

Child creates a child Injector whose bindings overlay those of the parent.

The parent will never be modified by the child.

func (*Injector) Get

func (i *Injector) Get(t reflect.Type) interface{}

Get acquires a value of type t from the injector.

It is usually preferable to use Call().

func (*Injector) Install

func (i *Injector) Install(modules ...interface{}) Binder

Install a module. A module is a struct whose methods are providers. This is useful for grouping configuration data together with providers.

Duplicate modules are allowed as long as all fields are identical or either the existing module, or the new module, are zero value.

Any method starting with "Provide" will be bound as a Provider. If the method name contains "Multi" it will not be a singleton provider. If the method name contains "Sequence" it must return a slice which is merged with slices of the same type. If the method name contains "Mapping" it must return a mapping which will be merged with mappings of the same type. Mapping and Sequence can not be used simultaneously.

Arguments to provider methods are injected.

For example, the following method will be called only once:

ProvideLog() *log.Logger { return log.New(...) }

While this method will be called each time a *log.Logger is injected.

ProvideMultiLog() *log.Logger { return log.New(...) }

func (*Injector) Safe

func (i *Injector) Safe() *SafeInjector

Safe returns the underlying SafeInjector.

func (*Injector) Validate

func (i *Injector) Validate(f interface{}) error

Validate that the function f can be called by the injector.

type Module

type Module interface {
	Configure(binder Binder) error
}

A Module implementing this interface will have its Configure() method called at Install() time.

type SafeBinder

type SafeBinder interface {
	Bind(things ...interface{}) error
	BindTo(to interface{}, impl interface{}) error
	Install(module ...interface{}) error
}

type SafeInjector

type SafeInjector struct {
	// contains filtered or unexported fields
}

SafeInjector is an IoC container.

func SafeNew

func SafeNew() *SafeInjector

SafeNew creates a new SafeInjector.

The injector itself is already bound, as is an implementation of the Binder interface.

func (*SafeInjector) Bind

func (s *SafeInjector) Bind(things ...interface{}) error

Bind binds a value to the injector. See Injector.Bind() for details.

func (*SafeInjector) BindTo

func (s *SafeInjector) BindTo(as interface{}, impl interface{}) error

BindTo binds an implementation to an interface. See Injector.BindTo() for details.

func (*SafeInjector) Call

func (s *SafeInjector) Call(f interface{}) ([]interface{}, error)

Call f, injecting any arguments.

func (*SafeInjector) Child

func (s *SafeInjector) Child() *SafeInjector

Child creates a child SafeInjector whose bindings overlay those of the parent.

The parent will never be modified by the child.

func (*SafeInjector) Get

func (s *SafeInjector) Get(t interface{}) (interface{}, error)

Get acquires a value of type t from the injector.

It is usually preferable to use Call().

func (*SafeInjector) Install

func (s *SafeInjector) Install(modules ...interface{}) (err error)

Install installs a module. See Injector.Install() for details.

func (*SafeInjector) Unsafe

func (s *SafeInjector) Unsafe() *Injector

func (*SafeInjector) Validate

func (s *SafeInjector) Validate(f interface{}) error

Validate that the function f can be called by the injector.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL