Description
I've been working on ideas for migrating some of our dependency injection code to using wire, and one awkwardness is when a service depends on a set of heterogeneous components that all implement the same interface.
I could see this kind of issue arising various kinds of value (for example with an HTTP server that composes a bunch of different handlers into a single handler), but this particular motivating example is about composing a service from long-lived workers.
At the top level, a service is composed of a set of workers that each implement some aspect of the server. The workers all implement the same interface and are managed by some top level management code that deals with worker restart, logging etc, but otherwise the workers have a disparate set of dependencies.
Given that each worker is implemented by a separate type, I could do something like this:
func newRunner(
w1 *WorkerType1,
w2 *WorkerType2,
w3 *WorkerType3,
) *worker.Runner {
runner := worker.NewRunner()
runner.Add(w1)
runner.Add(w2)
runner.Add(w3)
return runner
}
but this is tedious and a little error prone (if you add an extra worker, you need to remember to add a line to add it to the runner)
So I'm wondering about using a bit of reflection:
package worker
// AddStruct adds a struct value containing worker functions
// to start in the runner. Each exported field that implements
// the Worker interface is treated as a worker and added to the runner
// as if with Add(fieldName, fieldValue).
//
// The worker name may be changed by using a struct tag
// like this:
//
// MyWorker MyWorkerType `worker:"my-worker"`
//
// A function-valued field may be omitted from consideration
// by using a tag with a hyphen (-):
//
// IgnoreThis func() `worker:"-"`
//
// If there are any non-ignored function-typed fields that don't
// fit the worker signature, AddStruct will panic.
func (r *Runner) AddStruct(x interface{})
So then I could acquire the worker dependencies with this (assuming there's a wire.Struct(Workers{}) somewhere in the wire set):
type Workers struct {
W1 *WorkerType1
W2 *WorkerType2
W3 *WorkerType3
}
func newRunner(w Workers) *Runner {
runner := worker.NewRunner()
runner.AddStruct(w)
return runner
}
However, reflection is never clear, and I'd prefer to avoid it in favour of generated code if possible, given that we're generating code already.
I can think of a few ways of doing this in wire, but I'll avoid proposing solutions and just leave this as an issue for now.