di

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2017 License: MIT Imports: 6 Imported by: 3

README

di GoDoc Reference Build Status codecov Go Report Card

di is a dependency injection framework for Go. di supplies several dependency lifetime caching policies, provides dependency aware http handlers compatible with net/http, and provides a way to clean up dependencies instantiated during an http request.

di only resolves dependencies which are interfaces, the resolver itself, http.ResponseWriter, and *http.Request.

Dependency Lifetimes

  • Singleton
    • only one instance created throughout the life of the resolver
  • PerDependency
    • a new instance created for each dependency encountered, every time Resolve() is called
  • PerHttpRequest
    • a new instance is created for each HTTP request, and reused for the duration of the request. The dependency has an option to implement an interface which will be called back once the request is over
  • PerResolve
    • a new instance is created for each call of Resolve(), and then re-used throughout that call. Subsequent calls to Resolve() create new instances

More about lifetimes

Http

  resolver, err := di.NewResolver(defs)
  // if err
  
  for _, handler := range handlers {
    // the http.ResponseWriter and *http.Request values are available as dependencies, 
    // the resolver is also available as a dependency as an di.IResolver
    // handler.fn => func(dep1 Dep1, dep2 Dep2, etc)
    httpFn, err := resolver.HttpHandler(handler.fn, errFn)
    // if err
    
    http.HandleFunc(handler.url, httpFn)
  }

A more complete example is available here

Types

di can resolve a dependency directly if known. The dependency instance follows the lifecycle caching rules of the resolver

  var someDependency Dependency
  resolveErr := resolver.Resolve(&someDependency)

Curry Funcs

di can curry the parameters of funcs with dependencies known to a resolver, returning a new func that only contains parameters the caller would like to supply themselves.

  func normalFunc(name string, dep Dep) (int, string) {
    // DoThing(name) == 5
    return dep.DoThing(name), name + "!"
  }
  
  ifunc, resolveErr := resolver.Curry(normalFunc)
  // if resolveErr
  
  value, msg := ifunc.(func (string)(int, string))("hello")
  // 5, "hello!"

Invoke

  resolveErr := resolver.Invoke(func (dep Dep){
    // do()
  })

If the func returns an error, and no error is encountered while resolving the dependencies, that error is returned instead

  resolveErr := resolver.Invoke(func (dep Dep) error {
     innerErr := dep1.Do()
     return innerErr
  })
  // resolveErr.Err == innerErr

Documentation

Overview

Package di is a dependency injection framework

di supplies several dependency lifetime caching policies, provides dependency aware http handlers compatible with net/http, and provides a way to clean up dependencies instantiated during an http request.

di only resolves dependencies which are interfaces, the resolver itself, http.ResponseWriter, and *http.Request.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Def

type Def struct {
	// Constructor must be a func of the signature:
	//		func Name(dependency*) (Interface, error?)
	// Examples:
	//    func Foo1() Dependency
	//    func Foo2(dep1 Dep1) Dependency
	//    func Foo3(dep1, dep2 Dep1) (Dependency, error)
	Constructor interface{}

	// Lifetime is the caching lifetime of the dependency once it has been
	// resolved
	Lifetime Lifetime
}

Def represents a dependency definition

func NewDef

func NewDef(constructor interface{}, lifetime Lifetime) *Def

NewDef creates a new dependency definition which can be added to a Defs collection. See Def.Constructor for the format of the constructor parameter

type Defs

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

Defs represents a collection of dependency definitions

func Join

func Join(ds ...*Defs) *Defs

Join combines two Defs collections together into a new Defs

func NewDefs

func NewDefs() *Defs

NewDefs creates a new Defs collection

func (*Defs) Add

func (d *Defs) Add(constructor interface{}, lifetime Lifetime) error

Add adds a dependency definition to this Defs collection. See Def.Constructor for the format of the constructor parameter

func (*Defs) AddAll

func (d *Defs) AddAll(defs []*Def) error

AddAll is a bulk version of Add

type ErrDefMissing

type ErrDefMissing struct {
	// Type is the type of dependency which could not be resolved
	Type reflect.Type
}

ErrDefMissing is returned when an attempt is made to resolve a type but no definition for the type was found in the resolver.

Implements the error interface

func (*ErrDefMissing) Error

func (edm *ErrDefMissing) Error() string

Error returns an error string describing the error encountered

type ErrResolve

type ErrResolve struct {
	// DependencyChain is the chain of types leading up to the
	// type that could not be resolved. Does not contain Type
	DependencyChain []reflect.Type
	// Err represents the error that caused the resolution error. If
	// a definition was missing this will be an *ErrDefMissing,
	// otherwise it will be whatever error was returned by the
	// dependency constructor
	Err error
	// Type is the type of dependency which could not be resolved
	Type reflect.Type
}

ErrResolve is returned when an attempt is made to resolve a type but an error is encountered while resolving the dependency. The error could either be returned from the dependency constructor or be because no definition for the requested type exists

func (*ErrResolve) String

func (er *ErrResolve) String() string

String returns an string describing the error encountered

type IHttpClosable

type IHttpClosable interface {
	// Di_HttpClose is called when an http request, in which the
	// implementing object was instantiated, completes
	Di_HttpClose()
}

IHttpClosable is an interface a dependency can implement if they would like a callback executed when an http request finishes

type IHttpResolver

type IHttpResolver interface {
	IResolver

	// HttpHandler creates a new http request handler from a fn containing
	// dependencies. The ResponseWriter and *Request are supplied as
	// dependencies of the container, and will be resolved in the supplied
	// func or one of its dependencies. errFn is an error handling func
	// which will be called if there is an err while resolving one of the
	// dependencies.
	//
	// The return values are the resolver bound http handler func, and
	// any error encountered while creating the handler func
	HttpHandler(fn interface{}, errFn func(*ErrResolve, http.ResponseWriter, *http.Request)) (func(http.ResponseWriter, *http.Request), error)
}

IHttpResolver is an IResolver which can also generate http request handlers that resolve their dependencies

Example (HttpHandler)
package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/clavoie/di"
)

type HttpDep interface {
	// optional: only dependencies that perform cleanup need to implement
	// IHttpClosable
	di.IHttpClosable
}

type HttpImpl struct{}

func (hi *HttpImpl) Di_HttpClose() {
	// called after the request has ended
}

func NewHttpDep() HttpDep { return new(HttpImpl) }

// PrintLogger is an implementation of di.ILogger
type PrintLogger struct{}

func NewILogger() di.ILogger { return new(PrintLogger) }

func (pl *PrintLogger) HttpDuration(resolveDuration time.Duration) {
	fmt.Println("time to resolve is: ", resolveDuration)
}

var deps = []*di.Def{
	di.NewDef(NewHttpDep, di.PerHttpRequest),
	di.NewDef(NewILogger, di.Singleton),
}

func NewHttpResolver() di.IHttpResolver {
	defs := di.NewDefs()
	err := defs.AddAll(deps)

	if err != nil {
		panic(err)
	}

	resolver, err := di.NewResolver(defs)
	if err != nil {
		panic(err)
	}

	return resolver
}

var resolver = NewHttpResolver()

func DepHandler(dep HttpDep)                         {}
func HttpHandler(http.ResponseWriter, *http.Request) {}
func WriteToLog(resolveErr *di.ErrResolve)           { /* etc */ }

func ErrHandler(err *di.ErrResolve, w http.ResponseWriter, r *http.Request) {
	WriteToLog(err)
	w.WriteHeader(http.StatusInternalServerError)
}

var urlDefs = []struct {
	url     string
	handler interface{}
}{
	{"/", DepHandler},
	{"/thing", HttpHandler},
}

func main() {
	for _, urlDef := range urlDefs {
		handleFunc, err := resolver.HttpHandler(urlDef.handler, ErrHandler)
		if err != nil {
			panic(err)
		}

		http.HandleFunc(urlDef.url, handleFunc)
	}

	// listen and serve
}
Output:

func NewResolver

func NewResolver(d *Defs) (IHttpResolver, error)

NewResolver returns a new instance of IHttpResolver from a collection of dependency definitions. An IResolver definition is added to the collection, and can be included as a dependency for resolved types and funcs

type ILogger

type ILogger interface {
	// HttpDuration is called after all the dependencies for an http
	// handler have been resolved, but before the http handler runs.
	// The parameter value is the time taken to resolve all the handlers
	// dependencies.
	HttpDuration(time.Duration)
}

ILogger is a logging interface that a client can implement to capture output and metrics on the performance of a di resolver.

When a resolver starts up it will look for a definition of ILogger. If present the hooks defined in ILogger will be called back on the implementation.

type IResolver

type IResolver interface {
	// Curry takes a func, resolves all parameters of the func which
	// are known to the container, and returns a new func with those
	// parameters supplied by the container.
	//
	// Explicitly:
	//   func foo(i int, dep Dep) int { ... }
	//   curryFoo, err := container.Curry(foo)
	//   if err { ... }
	//   val := curryFoo.(func(int) int)(4)
	Curry(fn interface{}) (interface{}, *ErrResolve)

	// Invoke resolves all known dependencies of func fn, and then
	// attempts to execute the func.
	//
	// If an error is encountered while resolving the dependencies of
	// fn an *ErrResolve is returned.
	//
	// If fn can be resolved and fn returns a single value which
	// is an error, that is returned inside ErrResolve.Err
	//
	// Otherwise nil is returned
	Invoke(fn interface{}) *ErrResolve

	// Resolve attempts to resolve a known dependency. The parameter
	// must be a pointer to an interface
	// type known to the resolver
	//
	// Example:
	//   var dep Dep
	//   err := container.Resolve(&dep)
	Resolve(ptrToIface interface{}) *ErrResolve
}

IResolver is an object which knows how to resolve dependency chains and instantiate the dependencies according to their cache policies

Example (Curry)
type Dep interface{}
newDep := func() Dep { return new(struct{}) }
defs := NewDefs()
err := defs.Add(newDep, PerDependency)
if err != nil {
	panic(err)
}

resolver, err := NewResolver(defs)
if err != nil {
	panic(err)
}

fn := func(msg string, dep Dep) string {
	return fmt.Sprintf("%v:%v %v", msg, reflect.TypeOf(dep), dep == nil)
}

ifn, resolveErr := resolver.Curry(fn)
if resolveErr != nil {
	panic(resolveErr)
}

var newFn func(string) string
newFn = ifn.(func(string) string)

fmt.Println(newFn("type"))
Output:

type:*struct {} false
Example (Invoke)
type Dep interface{}
newDep := func() Dep { return new(struct{}) }
defs := NewDefs()
err := defs.Add(newDep, PerDependency)
if err != nil {
	panic(err)
}

resolver, err := NewResolver(defs)
if err != nil {
	panic(err)
}

resolveErr := resolver.Invoke(func(dep Dep) {
	fmt.Println(reflect.TypeOf(dep))
})

if resolveErr != nil {
	panic(resolveErr)
}

myErr := fmt.Errorf("my error")
resolveErr = resolver.Invoke(func(dep Dep) error { return myErr })

if resolveErr.Err != myErr {
	panic(myErr)
}
Output:

*struct {}
Example (Resolve)
type Dep interface{}
newDep := func() Dep { return new(struct{}) }
defs := NewDefs()
err := defs.Add(newDep, PerDependency)
if err != nil {
	panic(err)
}

resolver, err := NewResolver(defs)
if err != nil {
	panic(err)
}

var dep Dep
resolveErr := resolver.Resolve(&dep)
if resolveErr != nil {
	panic(resolveErr)
}

fmt.Println(dep == nil, reflect.TypeOf(dep))

var resolver2 IResolver
resolveErr = resolver.Resolve(&resolver2)
if resolveErr != nil {
	panic(resolveErr)
}

fmt.Println(resolver2 == nil)
Output:

false *struct {}
false

type Lifetime

type Lifetime int

Lifetime indicates the caching policy for resolved types

Example
package main

import (
	"fmt"

	"github.com/clavoie/di"
)

type Singleton interface {
	Value() int
}
type PerDependency interface {
	Value() int
}
type PerResolve interface {
	Value() int
}
type Impl struct{ value int }

func (li *Impl) Value() int { return li.value }

type Dependent interface{}
type DependentImpl struct {
	S1, S2 Singleton
	D1, D2 PerDependency
	R1, R2 PerResolve
}

func NewDependent(s1, s2 Singleton, d1, d2 PerDependency, r1, r2 PerResolve) Dependent {
	return &DependentImpl{s1, s2, d1, d2, r1, r2}
}

func main() {
	defs := di.NewDefs()
	counter := 0
	newImpl := func() *Impl {
		counter += 1
		return &Impl{counter}
	}
	newSingleton := func() Singleton { return (Singleton)(newImpl()) }
	newPerDependency := func() PerDependency { return (PerDependency)(newImpl()) }
	newPerResolve := func() PerResolve { return (PerResolve)(newImpl()) }
	deps := []*di.Def{
		di.NewDef(newSingleton, di.Singleton),
		di.NewDef(newPerDependency, di.PerDependency),
		di.NewDef(newPerResolve, di.PerResolve),
		di.NewDef(NewDependent, di.PerDependency),
	}
	err := defs.AddAll(deps)

	if err != nil {
		panic(err)
	}

	resolver, err := di.NewResolver(defs)
	if err != nil {
		panic(err)
	}

	var dependent Dependent
	resolveErr := resolver.Resolve(&dependent)
	if resolveErr != nil {
		panic(resolveErr)
	}

	// first resolution
	impl := dependent.(*DependentImpl)
	fmt.Println(impl.S1 == impl.S2, impl.S1.Value(), impl.S2.Value())
	fmt.Println(impl.D1 == impl.D2, impl.D1.Value(), impl.D2.Value())
	fmt.Println(impl.R1 == impl.R2, impl.R1.Value(), impl.R2.Value())

	resolveErr = resolver.Resolve(&dependent)
	if resolveErr != nil {
		panic(resolveErr)
	}

	impl2 := dependent.(*DependentImpl)
	fmt.Println(impl2.S1 == impl2.S2, impl2.S1.Value(), impl2.S2.Value())
	fmt.Println(impl2.D1 == impl2.D2, impl2.D1.Value(), impl2.D2.Value())
	fmt.Println(impl2.R1 == impl2.R2, impl2.R1.Value(), impl2.R2.Value())

	fmt.Println(impl.S1 == impl2.S1)
}
Output:

true 1 1
false 2 3
true 4 4
true 1 1
false 5 6
true 7 7
true
const (
	// Singleton indicates only one instance of the type
	// should be created ever, and used for every dependency
	// encountered going forward
	Singleton Lifetime = iota

	// PerDependency indicates that a new instance of the
	// type should be created for each dependency encountered.
	//
	// Explicitly:
	//		func NewFoo(dep1, dep2 Dep) Foo
	//
	// dep1 and dep2 will be two separate instances of Dep
	PerDependency

	// PerHttpRequest indicates that a new instance of the type
	// should be created per http request.
	//
	// Explicitly:
	//		func NewFoo(dep1, dep2 Dep) Foo
	//
	// if Foo is resolved in an http request dep1 and dep2 will be
	// the exact same instance of Dep. If not resolved via an http
	// request PerHttprequest acts like PerResolve
	PerHttpRequest

	// PerResolve indicates that a new instance of the type should
	// be created per call to Resolve(), but that the same instance
	// of the type should be used throughout the Resolve() call
	//
	// Explicitly:
	//		func NewFoo(dep1, dep2 Dep) Foo
	//		err1 := container.Resolve(&foo1)
	//		err2 := container.Resolve(&foo2)
	//		foo1.dep1 == foo1.dep2
	//		foo1.dep1 != foo2.dep1
	PerResolve
)

Jump to

Keyboard shortcuts

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