Dependency injection
DI is a dependency injection library that is focused on clean API and flexibility. DI has two types of top-level abstractions: Container and Resolver.
First one is responsible for accepting constructors and implementations and creating abstraction bindings out of them.
Second implements different implementation resolution scenarios against one or more Containers.
Initially this library was heavily inspired by GoLobby Container but since then
had a lot of backwards incompatible changes in structure, functionality and API.
To install DI simply run in your project directory:
go get github.com/HnH/di
Container
type Container interface {
Singleton(constructor any, opts ...Option) error
Factory(constructor any, opts ...Option) error
Implementation(implementation any, opts ...Option) error
ListBindings(reflect.Type) (map[string]Binding, error)
Reset()
}
Singleton
Singleton()
method requires a constructor which will return Implementation(s) of Abstraction(s). Constructor will be called once
and returned Implementations(s) will later always bound to Abstraction(s) on resolution requests.
err = di.Singleton(func() (Abstraction, SecondAbstraction) {
return Implementation, SecondImplementation
})
err = di.Singleton(func() (Abstraction) {
return Implementation
}, di.WithName("customName"))
err = di.Singleton(func() (Abstraction, SecondAbstraction) {
return Implementation, SecondImplementation
}, di.WithName("customName", "secondCustomName"))
err = di.Singleton(func() (Abstraction) {
return Implementation
}, di.WithName("customName", "secondCustomName"))
err = di.Singleton(func() (Abstraction) {
return Implementation
}, di.WithFill())
Factory
Factory()
method requires a constructor which will return exactly one Implementation of exactly one Abstraction.
Constructor will be called on each Abstraction resolution request.
err = di.Factory(func() (Abstraction) {
return Implementation
})
err := di.Factory(func() (Abstraction) {
return Implementation
}, di.WithName("customName"))
err = di.Factory(func() (Abstraction) {
return Implementation
}, di.WithFill())
Implementation
Implementation()
receives ready instance and binds it to its real type, which means that declared abstract variable type (interface) is ignored.
var circle Shape = newCircle()
err = di.Implementation(circle)
var a Shape
err = di.Resolve(&a)
var c *Circle
err = di.Resolve(&a)
err = di.Implementation(circle, di.WithName("customName"))
err = di.Resolve(&c, di.WithName("customName"))
Resolver
type Resolver interface {
With(implementations ...any) Resolver
Resolve(receiver any, opts ...Option) error
Call(function any, opts ...Option) error
Fill(receiver any) error
}
With
With()
takes a list of instantiated implementations and tries to use them in resolving scenarios.
In the opposite to Container's Implementation()
method With()
does not put instances into container and does not reflect a type on a binding time.
Instead of this it reuses reflect.Type.AssignableTo()
method capabilities on abstraction resolution time.
var circle Shape = newCircle()
err = di.Implementation(circle)
di.Call(func(s Shape) { return })
di.With(circle).Call(func(s Shape) { return }))
Resolve
Resolve()
requires a receiver (pointer) of an Abstraction and fills it with appropriate Implementation.
var abs Abstraction
err = di.Resolve(&a)
err = di.Resolve(&a, di.WithName("customName"))
Call
The Call()
executes as function
with resolved Implementation as a arguments.
err = di.Call(func(a Abstraction) {
})
var db Database
err = di.Call(func(a Abstraction) Database {
return &MySQL{a}
}, di.WithReturn(&db))
Fill
The Fill()
method takes a struct (pointer) and resolves its fields. The example below expresses how the Fill()
method works.
err = di.Singleton(func() Mailer { return &someMailer{} })
err = di.Singleton(func() (Database, Database) {
return &MySQL{}, &Redis{}
}, di.WithName("data", "cache"))
type App struct {
mailer Mailer `di:"type"`
data Database `di:"name"`
cache Database `di:"name"`
inner struct {
cache Database `di:"name"`
} `di:"recursive"`
another struct {
cache Database `di:"name"`
}
}
var App = App{}
err = container.Fill(&myApp)
Notice that by default Fill()
method returns error if unable to resolve any struct fields.
If one of the fields if optional, omitempty suffix should be added to the di tag.
type App struct {
mailer Mailer `di:"type,omitempty"`
}
Alternatively map[string]Type or []Type can be provided. It will be filled with all available implementations of provided Type.
var list []Shape
container.Fill(&list)
var list map[string]Shape
container.Fill(&list)
Provider
Provider is an abstraction of an entity that provides something to Container
type Provider interface {
Provide(Container) error
}
Constructor
Constructor implements a Construct()
method which is called either after binding to container in case of singleton, either after factory method was called.
Note that context.Context
must be provided in container before Constructor method can be called.
type Constructor interface {
Construct(context.Context) error
}
Context propagation
type Context interface {
Put(Container) Context
Container() Container
Resolver() Resolver
Raw() context.Context
}
Context propagation is possible via di.Context
abstraction. Quick example:
var container = di.NewContainer()
container.Implementation(newCircle())
var (
ctx = di.Ctx(context.Background).Put(container)
shp Shape
)
err = ctx.Resolver().Resolve(&shp)