diff --git a/README.md b/README.md index 118f866..106b367 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -# Wire: Compile-Time Dependency Injection for Go +# Wire: Automated Initialization in Go -Wire is a static [dependency injection][] framework for Go, inspired by -[Dagger][]. It works by using Go code to specify dependencies, then -generating code to create those structures, mimicking the code that a user -might have hand-written. +Wire is a code generation tool that automates connecting components using +[dependency injection][]. Dependencies between components are represented in +Wire as function parameters, encouraging explicit initialization instead of +global variables. Because Wire operates without runtime state or reflection, +code written to be used with Wire is useful even for hand-written +initialization. [dependency injection]: https://en.wikipedia.org/wiki/Dependency_injection -[Dagger]: https://google.github.io/dagger/ -## Usage Guide +## Basics + +Wire has two core concepts: providers and injectors. ### Defining Providers @@ -28,6 +31,9 @@ func ProvideFoo() Foo { } ``` +Provider functions must be exported in order to be used from other packages, +just like ordinary functions. + Providers can specify dependencies with parameters: ```go @@ -70,8 +76,9 @@ func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) { } ``` -Providers can be grouped in **provider sets**. To add these providers to a new -set called `SuperSet`, use the `wire.NewSet` function: +Providers can be grouped into **provider sets**. This is useful if several +providers will frequently be used together. To add these providers to a new set +called `SuperSet`, use the `wire.NewSet` function: ```go package foobarbaz @@ -116,8 +123,7 @@ say that the above providers were defined in a package called ```go // +build wireinject - -// ^ build tag makes sure the stub is not built in the final build +// The build tag makes sure the stub is not built in the final build. package main @@ -144,26 +150,17 @@ the generated file. You can generate the injector by invoking `gowire` in the package directory: -``` +```shell gowire ``` -Or you can add the line `//go:generate gowire` to another file in your package to -use [`go generate`]: - -``` -go generate -``` - -(Adding the line to the injection declaration file will be silently ignored by -`go generate`.) - Wire will produce an implementation of the injector in a file called `wire_gen.go` that looks something like this: ```go // Code generated by gowire. DO NOT EDIT. +//go:generate gowire //+build !wireinject package main @@ -187,55 +184,23 @@ As you can see, the output is very close to what a developer would write themselves. Further, there is little dependency on Wire at runtime: all of the written code is just normal Go code, and can be used without Wire. +Once `wire_gen.go` is created, you can regenerate it by running [`go generate`]. + [`go generate`]: https://blog.golang.org/generate -## Best Practices - -Wire is still not mature yet, but guidance that applies to Dagger generally -applies to Wire as well. In particular, when thinking about how to group -providers into sets, follow the same [guidance](https://google.github.io/dagger/testing.html#organize-modules-for-testability) -as Dagger (provider sets are called modules in Dagger/Guice): - -> Some [...] bindings will have reasonable alternatives, especially for -> testing, and others will not. For example, there are likely to be -> alternative bindings for a type like `AuthManager`: one for testing, others -> for different authentication/authorization protocols. -> -> But on the other hand, if the `AuthManager` interface has a method that -> returns the currently logged-in user, you might want to [export a provider of -> `User` that simply calls `CurrentUser()`] on the `AuthManager`. That -> published binding is unlikely to ever need an alternative. -> -> Once you’ve classified your bindings into [...] bindings with reasonable -> alternatives [and] bindings without reasonable alternatives, consider -> arranging them into provider sets like this: -> -> - One [provider set] for each [...] binding with a reasonable alternative. -> (If you are also writing the alternatives, each one gets its own [provider -> set].) That [provider set] contains exactly one provider. -> - All [...] bindings with no reasonable alternatives go into [provider sets] -> organized along functional lines. -> - The [provider sets] should each include the no-reasonable-alternative -> [provider sets] that require the [...] bindings each provides. - -One Wire-specific practice though: create one-off types where in Java you -would use a binding annotation. For example, if you need to pass a string -through the dependency graph, you would create a wrapping type: - -```go -type MySQLConnectionString string -``` - ## Advanced Features +The following features all build on top of the concepts of providers and +injectors. + ### Binding Interfaces -Frequently, dependency injection is used to bind concrete implementations for an -interface. Wire matches inputs to outputs via [type identity][], so the +Frequently, dependency injection is used to bind a concrete implementation for +an interface. Wire matches inputs to outputs via [type identity][], so the inclination might be to create a provider that returns an interface type. However, this would not be idiomatic, since the Go best practice is to [return -concrete types][]. Instead, you can declare an interface binding in a -provider set: +concrete types][]. Instead, you can declare an interface binding in a provider +set: ```go type Fooer interface { @@ -348,7 +313,8 @@ an error if the expression calls any functions. If a provider creates a value that needs to be cleaned up (e.g. closing a file), then it can return a closure to clean up the resource. The injector will use this to either return an aggregated cleanup function to the caller or to clean -up the resource if a later provider returns an error. +up the resource if a provider called later in the injector's implementation +returns an error. ```go func provideFile(log Logger, path Path) (*os.File, func(), error) { @@ -379,3 +345,37 @@ func injectFoo() Foo { panic(wire.Build(/* ... */)) } ``` + +## Best Practices + +The following are practices we recommend for 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. For example: + +```go +type MySQLConnectionString string +``` + +### Options Structs + +A provider function that includes many dependencies can pair the function with +an options struct. + +```go +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(Options{}, NewGreeter) +``` diff --git a/internal/wire/wire.go b/internal/wire/wire.go index 7e7ceab..f98a9b9 100644 --- a/internal/wire/wire.go +++ b/internal/wire/wire.go @@ -168,7 +168,10 @@ func (g *gen) frame() []byte { return nil } var buf bytes.Buffer - buf.WriteString("// Code generated by gowire. DO NOT EDIT.\n\n//+build !wireinject\n\npackage ") + buf.WriteString("// Code generated by gowire. DO NOT EDIT.\n\n") + buf.WriteString("//go:generate gowire\n") + buf.WriteString("//+build !wireinject\n\n") + buf.WriteString("package ") buf.WriteString(g.prog.Package(g.currPackage).Pkg.Name()) buf.WriteString("\n\n") if len(g.imports) > 0 {