Best practices
The following are practices we recommend when using Wire. This list will grow over time.
Distinguishing types
If you need to inject a common type like string, create a new string type to
avoid conflicts with other providers:
type MySQLConnectionString stringOptions structs
A provider function that includes many dependencies can pair the function with an options struct:
type Options struct {
// Messages is the set of recommended greetings.
Messages []Message
// Writer is the location to send greetings. nil goes to stdout.
Writer io.Writer
}
func NewGreeter(ctx context.Context, opts *Options) (*Greeter, error) {
// ...
}
var GreeterSet = wire.NewSet(wire.Struct(new(Options), "*"), NewGreeter)Provider sets in libraries
When creating a provider set for use in a library, the only changes you can make without breaking compatibility are:
- Change which provider a provider set uses to provide a specific output, as long as it does not introduce a new input to the provider set. It may remove inputs. However, note that existing injectors will use the old provider until they are regenerated.
- Introduce a new output type into the provider set, but only if the type itself is newly added. If the type is not new, it is possible that some injector already has the output type included, which would cause a conflict.
All other changes are not safe. This includes:
- Requiring a new input in the provider set.
- Removing an output type from a provider set.
- Adding an existing output type into the provider set.
Instead of making one of these breaking changes, consider adding a new provider set.
As an example, if you have a provider set like this:
var GreeterSet = wire.NewSet(NewStdoutGreeter)
func DefaultGreeter(ctx context.Context) *Greeter {
// ...
}
func NewStdoutGreeter(ctx context.Context, msgs []Message) *Greeter {
// ...
}
func NewGreeter(ctx context.Context, w io.Writer, msgs []Message) (*Greeter, error) {
// ...
}You may:
- Use
DefaultGreeterinstead ofNewStdoutGreeterinGreeterSet. - Create a new type
Tand add a provider forTtoGreeterSet, as long asTis introduced in the same commit/release as the provider is added.
You may not:
- Use
NewGreeterinstead ofNewStdoutGreeterinGreeterSet. This both adds an input type (io.Writer) and requires injectors to return anerrorwhere the provider of*Greeterdid not require this before. - Remove
NewStdoutGreeterfromGreeterSet. Injectors depending on*Greeterwill be broken. - Add a provider for
io.WritertoGreeterSet. Injectors might already have a provider forio.Writerwhich might conflict with this one.
As such, you should pick the output types in a library provider set carefully. In
general, prefer small provider sets in a library. For example, it is common for a
library provider set to contain a single provider function along with a
wire.Bind to the interface the return type implements. Avoiding larger provider
sets reduces the likelihood that applications will encounter conflicts. To
illustrate, imagine your library provides a client for a web service. While it may
be tempting to bundle a provider for *http.Client in a provider set for your
library’s client, doing so would cause conflicts if every library did the same.
Instead, the library’s provider set should only include the provider for the API
client, and let *http.Client be an input of the provider set.
Mocking
There are two approaches for creating an injected app with mocked dependencies. Examples of both approaches are shown here.
Approach A: Pass mocks to the injector
Create a test-only injector that takes all of the mocks as arguments; the argument
types must be the interface types the mocks are mocking. wire.Build can’t include
providers for the mocked dependencies without creating conflicts, so if you’re
using provider set(s) you will need to define one that doesn’t include the mocked
types.
Approach B: Return the mocks from the injector
Create a new struct that includes the app plus all of the dependencies you want to
mock. Create a test-only injector that returns this struct, give it providers for
the concrete mock types, and use wire.Bind to tell Wire that the concrete mock
types should be used to fulfill the appropriate interface.