2018-03-26 07:39:00 -07:00
|
|
|
|
# goose: Compile-Time Dependency Injection for Go
|
|
|
|
|
|
|
|
|
|
|
|
goose is a compile-time [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.
|
|
|
|
|
|
|
|
|
|
|
|
[dependency injection]: https://en.wikipedia.org/wiki/Dependency_injection
|
|
|
|
|
|
[Dagger]: https://google.github.io/dagger/
|
|
|
|
|
|
|
|
|
|
|
|
## Usage Guide
|
|
|
|
|
|
|
|
|
|
|
|
### Defining Providers
|
|
|
|
|
|
|
|
|
|
|
|
The primary mechanism in goose is the **provider**: a function that can
|
|
|
|
|
|
produce a value, annotated with the special `goose:provide` directive. These
|
2018-03-28 17:31:32 -07:00
|
|
|
|
functions are otherwise ordinary Go code.
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
```go
|
2018-03-28 17:31:32 -07:00
|
|
|
|
package foobarbaz
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
type Foo int
|
|
|
|
|
|
|
|
|
|
|
|
// goose:provide
|
|
|
|
|
|
|
|
|
|
|
|
// ProvideFoo returns a Foo.
|
|
|
|
|
|
func ProvideFoo() Foo {
|
|
|
|
|
|
return 42
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
Providers are always part of a **provider set**: if there is no provider set
|
|
|
|
|
|
named on the `//goose:provide` line, then the provider is added to the provider
|
2018-03-28 17:49:12 -07:00
|
|
|
|
set with the same name as the function (`ProvideFoo`, in this case).
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
Providers can specify dependencies with parameters:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
2018-03-28 17:31:32 -07:00
|
|
|
|
package foobarbaz
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
type Bar int
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
// goose:provide SuperSet
|
|
|
|
|
|
|
2018-03-26 07:39:00 -07:00
|
|
|
|
// ProvideBar returns a Bar: a negative Foo.
|
|
|
|
|
|
func ProvideBar(foo Foo) Bar {
|
|
|
|
|
|
return Bar(-foo)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Providers can also return errors:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
2018-03-28 17:31:32 -07:00
|
|
|
|
package foobarbaz
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type Baz int
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
// goose:provide SuperSet
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
// ProvideBaz returns a value if Bar is not zero.
|
|
|
|
|
|
func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
|
|
|
|
|
|
if bar == 0 {
|
|
|
|
|
|
return 0, errors.New("cannot provide baz when bar is zero")
|
|
|
|
|
|
}
|
|
|
|
|
|
return Baz(bar), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2018-03-28 17:49:12 -07:00
|
|
|
|
Provider sets can import other provider sets. To add the `ProvideFoo` set to
|
2018-03-28 17:31:32 -07:00
|
|
|
|
`SuperSet`:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
2018-03-28 17:49:12 -07:00
|
|
|
|
// goose:import SuperSet ProvideFoo
|
2018-03-28 17:31:32 -07:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
You can also import provider sets in another package, provided that you have a
|
|
|
|
|
|
Go import for the package:
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
```go
|
2018-03-28 17:31:32 -07:00
|
|
|
|
// goose:import SuperSet "example.com/some/other/pkg".OtherSet
|
2018-03-26 07:39:00 -07:00
|
|
|
|
```
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
A provider set reference is an optional import qualifier (either a package name
|
|
|
|
|
|
or a quoted import path, as seen above) ending with a dot, followed by the
|
|
|
|
|
|
provider set name.
|
|
|
|
|
|
|
2018-03-26 07:39:00 -07:00
|
|
|
|
### Injectors
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
An application wires up these providers with an **injector**: a function that
|
|
|
|
|
|
calls providers in dependency order. With goose, you write the injector's
|
|
|
|
|
|
signature, then goose generates the function's body.
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
An injector is declared by writing a function declaration without a body in a
|
|
|
|
|
|
file guarded by a `gooseinject` build tag. Let's say that the above providers
|
2018-03-28 17:31:32 -07:00
|
|
|
|
were defined in a package called `example.com/foobarbaz`. The following would
|
2018-03-26 07:39:00 -07:00
|
|
|
|
declare an injector to obtain a `Baz`:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
//+build gooseinject
|
|
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
"example.com/foobarbaz"
|
2018-03-26 07:39:00 -07:00
|
|
|
|
)
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
// goose:use foobarbaz.SuperSet
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
func initializeApp(ctx context.Context) (foobarbaz.Baz, error)
|
2018-03-26 07:39:00 -07:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Like providers, injectors can be parameterized on inputs (which then get sent to
|
2018-03-28 17:31:32 -07:00
|
|
|
|
providers) and can return errors. Each `goose:use` directive specifies a
|
|
|
|
|
|
provider set to use in the injection. An injector can have one or more
|
|
|
|
|
|
`goose:use` directives. `goose:use` directives use the same syntax as
|
|
|
|
|
|
`goose:import` to reference provider sets.
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
You can generate the injector by invoking goose in the package directory:
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
goose
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Or you can add the line `//go:generate goose` 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`.)
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
goose will produce an implementation of the injector in a file called
|
|
|
|
|
|
`goose_gen.go` that looks something like this:
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// Code generated by goose. DO NOT EDIT.
|
|
|
|
|
|
|
|
|
|
|
|
//+build !gooseinject
|
|
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2018-03-28 17:31:32 -07:00
|
|
|
|
"example.com/foobarbaz"
|
2018-03-26 07:39:00 -07:00
|
|
|
|
)
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
func initializeApp(ctx context.Context) (foobarbaz.Baz, error) {
|
|
|
|
|
|
foo := foobarbaz.ProvideFoo()
|
|
|
|
|
|
bar := foobarbaz.ProvideBar(foo)
|
|
|
|
|
|
baz, err := foobarbaz.ProvideBaz(ctx, bar)
|
2018-03-26 07:39:00 -07:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return 0, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return baz, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
As you can see, the output is very close to what a developer would write
|
|
|
|
|
|
themselves. Further, there is no dependency on goose at runtime: all of the
|
|
|
|
|
|
written code is just normal Go code, and can be used without goose.
|
|
|
|
|
|
|
|
|
|
|
|
[`go generate`]: https://blog.golang.org/generate
|
|
|
|
|
|
|
|
|
|
|
|
## Best Practices
|
|
|
|
|
|
|
|
|
|
|
|
goose is still not mature yet, but guidance that applies to Dagger generally
|
2018-03-28 17:31:32 -07:00
|
|
|
|
applies to goose 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):
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
> 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
|
2018-03-28 17:31:32 -07:00
|
|
|
|
> arranging them into provider sets like this:
|
2018-03-26 07:39:00 -07:00
|
|
|
|
>
|
2018-03-28 17:31:32 -07:00
|
|
|
|
> - 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]
|
2018-03-26 07:39:00 -07:00
|
|
|
|
> organized along functional lines.
|
2018-03-28 17:31:32 -07:00
|
|
|
|
> - The [provider sets] should each include the no-reasonable-alternative
|
|
|
|
|
|
> [provider sets] that require the [...] bindings each provides.
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
|
|
|
|
|
One goose-specific practice though: create one-off types where in Java you
|
2018-03-28 17:31:32 -07:00
|
|
|
|
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
|
|
|
|
|
|
```
|
2018-03-26 07:39:00 -07:00
|
|
|
|
|
2018-03-30 21:34:08 -07:00
|
|
|
|
## Advanced Features
|
|
|
|
|
|
|
2018-04-02 14:08:17 -07:00
|
|
|
|
### Binding Interfaces
|
|
|
|
|
|
|
|
|
|
|
|
Frequently, dependency injection is used to bind concrete implementations for an
|
|
|
|
|
|
interface. goose 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:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type Fooer interface {
|
|
|
|
|
|
Foo() string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type Bar string
|
|
|
|
|
|
|
|
|
|
|
|
func (b *Bar) Foo() string {
|
|
|
|
|
|
return string(*b)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//goose:provide BarFooer
|
|
|
|
|
|
func provideBar() *Bar {
|
|
|
|
|
|
b := new(Bar)
|
|
|
|
|
|
*b = "Hello, World!"
|
|
|
|
|
|
return b
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//goose:bind BarFooer Fooer *Bar
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The syntax is provider set name, interface type, and finally the concrete type.
|
|
|
|
|
|
An interface binding does not necessarily need to have a provider in the same
|
|
|
|
|
|
set that provides the concrete type.
|
|
|
|
|
|
|
|
|
|
|
|
[type identity]: https://golang.org/ref/spec#Type_identity
|
|
|
|
|
|
[return concrete types]: https://github.com/golang/go/wiki/CodeReviewComments#interfaces
|
|
|
|
|
|
|
2018-04-03 21:11:53 -07:00
|
|
|
|
### Struct Providers
|
|
|
|
|
|
|
|
|
|
|
|
Structs can also be marked as providers. Instead of calling a function, an
|
|
|
|
|
|
injector will fill in each field using the corresponding provider. For a given
|
|
|
|
|
|
struct type `S`, this would provide both `S` and `*S`. For example, given the
|
|
|
|
|
|
following providers:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type Foo int
|
|
|
|
|
|
type Bar int
|
|
|
|
|
|
|
|
|
|
|
|
//goose:provide Foo
|
|
|
|
|
|
|
|
|
|
|
|
func provideFoo() Foo {
|
|
|
|
|
|
// ...
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//goose:provide Bar
|
|
|
|
|
|
|
|
|
|
|
|
func provideBar() Bar {
|
|
|
|
|
|
// ...
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//goose:provide
|
|
|
|
|
|
|
|
|
|
|
|
type FooBar struct {
|
|
|
|
|
|
Foo Foo
|
|
|
|
|
|
Bar Bar
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
A generated injector for `FooBar` would look like this:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func injectFooBar() FooBar {
|
|
|
|
|
|
foo := provideFoo()
|
|
|
|
|
|
bar := provideBar()
|
|
|
|
|
|
fooBar := FooBar{
|
|
|
|
|
|
Foo: foo,
|
|
|
|
|
|
Bar: bar,
|
|
|
|
|
|
}
|
|
|
|
|
|
return fooBar
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
And similarly if the injector needed a `*FooBar`.
|
|
|
|
|
|
|
2018-04-03 13:13:15 -07:00
|
|
|
|
### Cleanup 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.
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
//goose:provide
|
|
|
|
|
|
|
|
|
|
|
|
func provideFile(log Logger, path Path) (*os.File, func(), error) {
|
|
|
|
|
|
f, err := os.Open(string(path))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
cleanup := func() {
|
|
|
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
|
|
log.Log(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return f, cleanup, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
A cleanup function is guaranteed to be called before the cleanup function of any
|
|
|
|
|
|
of the provider's inputs and must have the signature `func()`.
|
|
|
|
|
|
|
2018-03-26 07:39:00 -07:00
|
|
|
|
## Future Work
|
|
|
|
|
|
|
2018-03-28 17:31:32 -07:00
|
|
|
|
- Support for map bindings.
|
|
|
|
|
|
- Support for multiple provider outputs.
|
2018-04-02 14:08:17 -07:00
|
|
|
|
- Tighter validation for a provider set (cycles in unused providers goes
|
|
|
|
|
|
unreported currently)
|
|
|
|
|
|
- Visualization for provider sets
|