A simple dependency injection library inspired by golobby
godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].
Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.
Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.
injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.
Note: the return value of the provider and the argument type passed to Resolve must match.
Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)
injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.
Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.
typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"You can invoke the injector to give you a concrete type for provided closure:
injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.
Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.
godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.
typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // trueDo not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.
The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.
This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.
Everything else is threadsafe and can be called across goroutines.
Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.
Ex.
injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.
By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:
injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.
- A huge thank you to Kevin Birk for the design and contributions to this repo.
- Thanks to golobby for inspiring this repo and serving as a code reference for how to use the
reflectpackage to accomplish sane dependency injection in go.