wire: clean up README (google/go-cloud#100)
Summary of changes: - Rewrote introduction (fixes google/go-cloud#81). - Simplified `go generate` explanation by automatically adding the comment line to the output. - Removed mentions of Dagger. Wire stands enough on its own now, but Dagger's influence lives on in our minds. - Moved best practices to bottom and removed provider set grouping guidance.
This commit is contained in:
128
README.md
128
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
|
Wire is a code generation tool that automates connecting components using
|
||||||
[Dagger][]. It works by using Go code to specify dependencies, then
|
[dependency injection][]. Dependencies between components are represented in
|
||||||
generating code to create those structures, mimicking the code that a user
|
Wire as function parameters, encouraging explicit initialization instead of
|
||||||
might have hand-written.
|
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
|
[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
|
### 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:
|
Providers can specify dependencies with parameters:
|
||||||
|
|
||||||
```go
|
```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
|
Providers can be grouped into **provider sets**. This is useful if several
|
||||||
set called `SuperSet`, use the `wire.NewSet` function:
|
providers will frequently be used together. To add these providers to a new set
|
||||||
|
called `SuperSet`, use the `wire.NewSet` function:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package foobarbaz
|
package foobarbaz
|
||||||
@@ -116,8 +123,7 @@ say that the above providers were defined in a package called
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
// +build wireinject
|
// +build wireinject
|
||||||
|
// The build tag makes sure the stub is not built in the final build.
|
||||||
// ^ build tag makes sure the stub is not built in the final build
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@@ -144,26 +150,17 @@ the generated file.
|
|||||||
|
|
||||||
You can generate the injector by invoking `gowire` in the package directory:
|
You can generate the injector by invoking `gowire` in the package directory:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
gowire
|
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 will produce an implementation of the injector in a file called
|
||||||
`wire_gen.go` that looks something like this:
|
`wire_gen.go` that looks something like this:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Code generated by gowire. DO NOT EDIT.
|
// Code generated by gowire. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:generate gowire
|
||||||
//+build !wireinject
|
//+build !wireinject
|
||||||
|
|
||||||
package main
|
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
|
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.
|
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
|
[`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
|
## Advanced Features
|
||||||
|
|
||||||
|
The following features all build on top of the concepts of providers and
|
||||||
|
injectors.
|
||||||
|
|
||||||
### Binding Interfaces
|
### Binding Interfaces
|
||||||
|
|
||||||
Frequently, dependency injection is used to bind concrete implementations for an
|
Frequently, dependency injection is used to bind a concrete implementation for
|
||||||
interface. Wire matches inputs to outputs via [type identity][], so the
|
an interface. Wire matches inputs to outputs via [type identity][], so the
|
||||||
inclination might be to create a provider that returns an interface type.
|
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
|
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
|
concrete types][]. Instead, you can declare an interface binding in a provider
|
||||||
provider set:
|
set:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Fooer interface {
|
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),
|
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
|
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
|
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
|
```go
|
||||||
func provideFile(log Logger, path Path) (*os.File, func(), error) {
|
func provideFile(log Logger, path Path) (*os.File, func(), error) {
|
||||||
@@ -379,3 +345,37 @@ func injectFoo() Foo {
|
|||||||
panic(wire.Build(/* ... */))
|
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)
|
||||||
|
```
|
||||||
|
|||||||
@@ -168,7 +168,10 @@ func (g *gen) frame() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
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(g.prog.Package(g.currPackage).Pkg.Name())
|
||||||
buf.WriteString("\n\n")
|
buf.WriteString("\n\n")
|
||||||
if len(g.imports) > 0 {
|
if len(g.imports) > 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user