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
type Defs ¶
type Defs struct {
// contains filtered or unexported fields
}
Defs represents a collection of dependency definitions
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 )