- There are Go packages that help with dependency injection, employing reflection.
- And other practices insist on passing everything as an argument, down the pipe; employing mostly factory methods.
- At the same time putting long-lived instances inside a context is not recommended - I like context for cancellation (and hierarchy of cancellations) and I like it for passing state when every step in a pipeline is concurrent and short lived; for other uses, using context seems a not proper choice.
Struggling with these thoughts, something like a hierarchical “service locator” (or is it?) came to my mind:
type DepRegistry struct {
parent *DepRegistry
deps sync.Map
}
func (r *DepRegistry) Register(name string, dep interface{}) {
r.deps.Store(name, dep)
}
// Load first loads from current instance, then tries
// to load from parent (panics if fails to find)
func (r *DepRegistry) Load(name string) interface{} {
dep, ok := r.deps.Load(name)
if !ok {
if r.parent != nil {
return r.parent.Load(name)
}
panic("unknown dep " + name)
}
return dep
}
func NewDepRegistry(parent ...*DepRegistry) *DepRegistry {
res := &DepRegistry{}
if len(parent) > 0 && parent[0] != nil {
res.parent = parent[0]
}
return res
}
I would love to hear thoughts on this matter. Is it an acceptable solution? Is there anything missing?