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:
Ross Light
2018-06-19 08:04:59 -07:00
parent b12449f9e3
commit cd52d44251
2 changed files with 68 additions and 65 deletions

128
README.md
View File

@@ -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 youve 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)
```