Closed carlosafonso closed 11 months ago
You wouldn't use wire.Bind()
in this case, since the concrete binding isn't known at compile-time. Having provider functions that return an interface type is how you'd solve for cases where the implementation is selected at runtime.
One design choice that I've found helpful in my non-trivial Wire-based projects is to have a *Config
type that handles CLI bindings and which is amenable to being composed should you have many different packages that all have some degree of configurability. The *Config
object is passed into the top-level injector function and subsequently made available to the provider stack. The use of a *Config
instead of individual, per-knob binding types makes the interface between a CLI package like cobra
less tedious and amenable to testing.
For example:
type Config struct {
SomeParam string
// ... more fields
}
func (c *Config) Bind(flags *pflag.FlagSet) { /* Attach flags to fields */ }
func (c *Config) Preflight() error { /* Set reasonable defaults */ }
func ProvideThing(cfg *Config) (Thing, error) {
// May return a different implementation of the Thing interface based on flags.
}
type MyStack {
Some Thing
Other *Doohicky
}
func MyInjector(cfg *Config) (*MyStack, error) {
panic(wire.Build(
ProvideThing(),
wire.Struct(new(MyStack), "*"),
// Other bindings as usual
))
}
func main() {
cfg := &Config{}
cmd := &cobra.Command{
RunE: func(*cobra.Command, []args) error {
if err := cfg.Preflight(); err != nil {
return err
}
stack, err := MyInjector(cfg)
// Do other things
}
}
cfg.Bind(cmd.Flags())
cmd.Execute()
}
For a larger example: https://github.com/cockroachdb/cdc-sink/blob/master/internal/source/cdc/server/wire_gen.go
Thank you Bob! Glad to hear we are doing pretty much the same, as I'm also relying on a *Config
type to capture settings from the user (I also happen to be getting them from env vars, but the idea remains).
(Apologies if this has been discussed somewhere else but I have been unable to find an answer to this.)
I'm learning Wire and trying to use it in my Go apps. Assume, for example, that my app calls a Generative AI model to do something. (This is just an example; this is not specifically tied to Generative AI or LLMs but I believe they make for a good analogy.)
As there are many Generative AI models out there, my app does not really care which one is used by the implementer as long as it implements
GenerateText()
, hence the dependency on an interface.Let's also assume this very same app also provides two built-in implementations of this interface:
GeminiModel
wraps code which uses Google Gemini, andChatGptModel
does the same for Open AI's ChatGPT.If I wanted to expose this as a CLI flag, or environment variable, the specific implementation would need to be resolved during injection. So, for example, the provider for the GenAiModel interface could be something like this:
And this works. However I understand this is not idiomatic, as we are returning an interface, not a concrete type.
I understand that
wire.Bind()
is used to bind interfaces to concrete implementations, however I'm not sure how it fits in my use case above.Am I understanding Wire correctly here? Am I doing things in the completely wrong way?