Simple hierarchical dependency injector based on go's reflection system and influenced by common dependency injection handlers.
Focuses on a clear and reusable structure, without pitfalls.
Currently in production in 3 of my larger projects (35k loc+).
Works very reliable and we never had any issues.
-- len
-
indirect dependancies via interface
-
direct dependancies via pointer
-
preparation of structs
-
constructor methods
-
100% test coverage
-
Travis-CI
Reminder: Services can depend on other services and references!
Create a new service definition. This way, it can be replaced during testing.
NamingService interface {
Names() []string
}
Create the default implementation for this service.
StaticNamingService struct {}
func (s *StaticNamingService) Names() []string {
return []{"Carl", "Michael", "Susanne"}
}
Create a new injector and provide instances of the service implementations.
injector := dieb.NewInjector()
defer injector.Shutdown()
injector.Provide(
&ConfigurationService{},
&StaticNamingService{},
)
Use the injector to fulfill dependencies.
type NamesController struct{
NamingService NamingService `dieb:""` // This is an indirect dependancy
StaticNamingService *StaticNamingService `dieb:""` // This is a direct dependancy
}
var ctrl NamesController
if err := injector.Prepare(&ctrl); err != nil {
panic(err)
}
log.Print(ctrl.NamingService.Names())
Since Version v2.1.0
it is possible to inject via method.
func ConstructorMethod(/* deps */) error {
// [...]
// Do something with the deps
// [...]
return nil
}
err := inj.PrepareFunc(ConstructorMethod)
if err != nil {
panic(err)
}
The Init(..) error
method will automatically be called when a service is provided!
Adding a new service with existing interfaces overwrites the previously existing one. Consequently, a previously used service can be injected in the newest one to be used.
BetterNamingService struct {
Previous NamingService `dieb:""`
}
func (s *BetterNamingService) Names() []string {
return append(s.Previous.Names(), "Vivian", "Marcus", "Nani"}
}
Will return ["Carl", "Michael", "Susanne", "Vivian", "Marcus", "Nani"] when used like this.
injector := dieb.NewInjector()
defer injector.Shutdown()
injector.Provide(&StaticNamingService{})
// ... do something else ...
injector.Provide(&BetterNamingSystem{})
A typical example could be a StorageService
and a CachingService
that provides a storage interface,
but applies a custom caching strategy.
When declaring dependencies with the dieb
annotation, the option dieb:",optional"
can be used to make the injector ignore the dependency if it can not be resolved.
Example
type SomeCriticalController struct{
logger Logger `dieb:",optional"`
}
Sometimes, it may be useful to construct or deconstruct services before and after usage. To bring this into the overall workflow
type DatabaseService struct{
mysql *sqlx.DB
}
// dieb.Initer
func (d *DatabaseService) Init() error {
// ... connect to the database
return nil
}
// custom Init with dependancies
func (d *DatabaseService) Init(injector dieb.Injector, db *SomeService) error {
// ... connect to the database
return nil
}
// dieb.Shutdowner
func (d *DatabaseService) Shutdown() {
// ... close all open connections ...
}
To work, defer injector.Shutdown()
is required.
[WIP]
None
02.12 - @joernlenoch