365 lines
12 KiB
Go
365 lines
12 KiB
Go
|
// Copyright (c) 2019 Uber Technologies, Inc.
|
||
|
//
|
||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
// of this software and associated documentation files (the "Software"), to deal
|
||
|
// in the Software without restriction, including without limitation the rights
|
||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
// copies of the Software, and to permit persons to whom the Software is
|
||
|
// furnished to do so, subject to the following conditions:
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be included in
|
||
|
// all copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
// THE SOFTWARE.
|
||
|
|
||
|
package fx
|
||
|
|
||
|
import "go.uber.org/dig"
|
||
|
|
||
|
// In can be embedded in a constructor's parameter struct to take advantage of
|
||
|
// advanced dependency injection features.
|
||
|
//
|
||
|
// Modules should take a single parameter struct that embeds an In in order to
|
||
|
// provide a forward-compatible API: since adding fields to a struct is
|
||
|
// backward-compatible, modules can then add optional dependencies in minor
|
||
|
// releases.
|
||
|
//
|
||
|
// # Parameter Structs
|
||
|
//
|
||
|
// Fx constructors declare their dependencies as function parameters. This can
|
||
|
// quickly become unreadable if the constructor has a lot of dependencies.
|
||
|
//
|
||
|
// func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler {
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// To improve the readability of constructors like this, create a struct that
|
||
|
// lists all the dependencies as fields and change the function to accept that
|
||
|
// struct instead. The new struct is called a parameter struct.
|
||
|
//
|
||
|
// Fx has first class support for parameter structs: any struct embedding
|
||
|
// fx.In gets treated as a parameter struct, so the individual fields in the
|
||
|
// struct are supplied via dependency injection. Using a parameter struct, we
|
||
|
// can make the constructor above much more readable:
|
||
|
//
|
||
|
// type HandlerParams struct {
|
||
|
// fx.In
|
||
|
//
|
||
|
// Users *UserGateway
|
||
|
// Comments *CommentGateway
|
||
|
// Posts *PostGateway
|
||
|
// Votes *VoteGateway
|
||
|
// AuthZ *AuthZGateway
|
||
|
// }
|
||
|
//
|
||
|
// func NewHandler(p HandlerParams) *Handler {
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// Though it's rarely a good idea, constructors can receive any combination of
|
||
|
// parameter structs and parameters.
|
||
|
//
|
||
|
// func NewHandler(p HandlerParams, l *log.Logger) *Handler {
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// # Optional Dependencies
|
||
|
//
|
||
|
// Constructors often have optional dependencies on some types: if those types are
|
||
|
// missing, they can operate in a degraded state. Fx supports optional
|
||
|
// dependencies via the `optional:"true"` tag to fields on parameter structs.
|
||
|
//
|
||
|
// type UserGatewayParams struct {
|
||
|
// fx.In
|
||
|
//
|
||
|
// Conn *sql.DB
|
||
|
// Cache *redis.Client `optional:"true"`
|
||
|
// }
|
||
|
//
|
||
|
// If an optional field isn't available in the container, the constructor
|
||
|
// receives the field's zero value.
|
||
|
//
|
||
|
// func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) {
|
||
|
// if p.Cache == nil {
|
||
|
// log.Print("Caching disabled")
|
||
|
// }
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// Constructors that declare optional dependencies MUST gracefully handle
|
||
|
// situations in which those dependencies are absent.
|
||
|
//
|
||
|
// The optional tag also allows adding new dependencies without breaking
|
||
|
// existing consumers of the constructor.
|
||
|
//
|
||
|
// # Named Values
|
||
|
//
|
||
|
// Some use cases require the application container to hold multiple values of
|
||
|
// the same type. For details on producing named values, see the documentation
|
||
|
// for the Out type.
|
||
|
//
|
||
|
// Fx allows functions to consume named values via the `name:".."` tag on
|
||
|
// parameter structs. Note that both the name AND type of the fields on the
|
||
|
// parameter struct must match the corresponding result struct.
|
||
|
//
|
||
|
// type GatewayParams struct {
|
||
|
// fx.In
|
||
|
//
|
||
|
// WriteToConn *sql.DB `name:"rw"`
|
||
|
// ReadFromConn *sql.DB `name:"ro"`
|
||
|
// }
|
||
|
//
|
||
|
// The name tag may be combined with the optional tag to declare the
|
||
|
// dependency optional.
|
||
|
//
|
||
|
// type GatewayParams struct {
|
||
|
// fx.In
|
||
|
//
|
||
|
// WriteToConn *sql.DB `name:"rw"`
|
||
|
// ReadFromConn *sql.DB `name:"ro" optional:"true"`
|
||
|
// }
|
||
|
//
|
||
|
// func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) {
|
||
|
// if p.ReadFromConn == nil {
|
||
|
// log.Print("Warning: Using RW connection for reads")
|
||
|
// p.ReadFromConn = p.WriteToConn
|
||
|
// }
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// # Value Groups
|
||
|
//
|
||
|
// To make it easier to produce and consume many values of the same type, Fx
|
||
|
// supports named, unordered collections called value groups. For details on
|
||
|
// producing value groups, see the documentation for the Out type.
|
||
|
//
|
||
|
// Functions can depend on a value group by requesting a slice tagged with
|
||
|
// `group:".."`. This will execute all constructors that provide a value to
|
||
|
// that group in an unspecified order, then collect all the results into a
|
||
|
// single slice. Keep in mind that this makes the types of the parameter and
|
||
|
// result struct fields different: if a group of constructors each returns
|
||
|
// type T, parameter structs consuming the group must use a field of type []T.
|
||
|
//
|
||
|
// type ServerParams struct {
|
||
|
// fx.In
|
||
|
//
|
||
|
// Handlers []Handler `group:"server"`
|
||
|
// }
|
||
|
//
|
||
|
// func NewServer(p ServerParams) *Server {
|
||
|
// server := newServer()
|
||
|
// for _, h := range p.Handlers {
|
||
|
// server.Register(h)
|
||
|
// }
|
||
|
// return server
|
||
|
// }
|
||
|
//
|
||
|
// Note that values in a value group are unordered. Fx makes no guarantees
|
||
|
// about the order in which these values will be produced.
|
||
|
//
|
||
|
// # Soft Value Groups
|
||
|
//
|
||
|
// A soft value group can be thought of as a best-attempt at populating the
|
||
|
// group with values from constructors that have already run. In other words,
|
||
|
// if a constructor's output type is only consumed by a soft value group,
|
||
|
// it will not be run.
|
||
|
//
|
||
|
// Note that Fx does not guarantee precise execution order of constructors
|
||
|
// or invokers, which means that the change in code that affects execution
|
||
|
// ordering of other constructors or functions will affect the values
|
||
|
// populated in this group.
|
||
|
//
|
||
|
// To declare a soft relationship between a group and its constructors, use
|
||
|
// the `soft` option on the group tag (`group:"[groupname],soft"`).
|
||
|
// This option is only valid for input parameters.
|
||
|
//
|
||
|
// type Params struct {
|
||
|
// fx.In
|
||
|
//
|
||
|
// Handlers []Handler `group:"server,soft"`
|
||
|
// Logger *zap.Logger
|
||
|
// }
|
||
|
//
|
||
|
// NewHandlerAndLogger := func() (Handler, *zap.Logger) { ... }
|
||
|
// NewHandler := func() Handler { ... }
|
||
|
// Foo := func(Params) { ... }
|
||
|
//
|
||
|
// app := fx.New(
|
||
|
// fx.Provide(fx.Annotate(NewHandlerAndLogger, fx.ResultTags(`group:"server"`))),
|
||
|
// fx.Provide(fx.Annotate(NewHandler, fx.ResultTags(`group::"server"`))),
|
||
|
// fx.Invoke(Foo),
|
||
|
// )
|
||
|
//
|
||
|
// The only constructor called is `NewHandlerAndLogger`, because this also provides
|
||
|
// `*zap.Logger` needed in the `Params` struct received by `Foo`. The Handlers
|
||
|
// group will be populated with a single Handler returned by `NewHandlerAndLogger`.
|
||
|
//
|
||
|
// In the next example, the slice `s` isn't populated as the provider would be
|
||
|
// called only because `strings` soft group value is its only consumer.
|
||
|
//
|
||
|
// app := fx.New(
|
||
|
// fx.Provide(
|
||
|
// fx.Annotate(
|
||
|
// func() (string, int) { return "hello", 42 },
|
||
|
// fx.ResultTags(`group:"strings"`),
|
||
|
// ),
|
||
|
// ),
|
||
|
// fx.Invoke(
|
||
|
// fx.Annotate(func(s []string) {
|
||
|
// // s will be an empty slice
|
||
|
// }, fx.ParamTags(`group:"strings,soft"`)),
|
||
|
// ),
|
||
|
// )
|
||
|
//
|
||
|
// In the next example, the slice `s` will be populated because there is a
|
||
|
// consumer for the same type which is not a `soft` dependency.
|
||
|
//
|
||
|
// app := fx.New(
|
||
|
// fx.Provide(
|
||
|
// fx.Annotate(
|
||
|
// func() string { "hello" },
|
||
|
// fx.ResultTags(`group:"strings"`),
|
||
|
// ),
|
||
|
// ),
|
||
|
// fx.Invoke(
|
||
|
// fx.Annotate(func(b []string) {
|
||
|
// // b is []string{"hello"}
|
||
|
// }, fx.ParamTags(`group:"strings"`)),
|
||
|
// ),
|
||
|
// fx.Invoke(
|
||
|
// fx.Annotate(func(s []string) {
|
||
|
// // s is []string{"hello"}
|
||
|
// }, fx.ParamTags(`group:"strings,soft"`)),
|
||
|
// ),
|
||
|
// )
|
||
|
//
|
||
|
// # Unexported fields
|
||
|
//
|
||
|
// By default, a type that embeds fx.In may not have any unexported fields. The
|
||
|
// following will return an error if used with Fx.
|
||
|
//
|
||
|
// type Params struct {
|
||
|
// fx.In
|
||
|
//
|
||
|
// Logger *zap.Logger
|
||
|
// mu sync.Mutex
|
||
|
// }
|
||
|
//
|
||
|
// If you have need of unexported fields on such a type, you may opt-into
|
||
|
// ignoring unexported fields by adding the ignore-unexported struct tag to the
|
||
|
// fx.In. For example,
|
||
|
//
|
||
|
// type Params struct {
|
||
|
// fx.In `ignore-unexported:"true"`
|
||
|
//
|
||
|
// Logger *zap.Logger
|
||
|
// mu sync.Mutex
|
||
|
// }
|
||
|
type In = dig.In
|
||
|
|
||
|
// Out is the inverse of In: it can be embedded in result structs to take
|
||
|
// advantage of advanced features.
|
||
|
//
|
||
|
// Modules should return a single result struct that embeds an Out in order to
|
||
|
// provide a forward-compatible API: since adding fields to a struct is
|
||
|
// backward-compatible, minor releases can provide additional types.
|
||
|
//
|
||
|
// # Result Structs
|
||
|
//
|
||
|
// Result structs are the inverse of parameter structs (discussed in the In
|
||
|
// documentation). These structs represent multiple outputs from a
|
||
|
// single function as fields. Fx treats all structs embedding fx.Out as result
|
||
|
// structs, so other constructors can rely on the result struct's fields
|
||
|
// directly.
|
||
|
//
|
||
|
// Without result structs, we sometimes have function definitions like this:
|
||
|
//
|
||
|
// func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) {
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// With result structs, we can make this both more readable and easier to
|
||
|
// modify in the future:
|
||
|
//
|
||
|
// type Gateways struct {
|
||
|
// fx.Out
|
||
|
//
|
||
|
// Users *UserGateway
|
||
|
// Comments *CommentGateway
|
||
|
// Posts *PostGateway
|
||
|
// }
|
||
|
//
|
||
|
// func SetupGateways(conn *sql.DB) (Gateways, error) {
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// # Named Values
|
||
|
//
|
||
|
// Some use cases require the application container to hold multiple values of
|
||
|
// the same type. For details on consuming named values, see the documentation
|
||
|
// for the In type.
|
||
|
//
|
||
|
// A constructor that produces a result struct can tag any field with
|
||
|
// `name:".."` to have the corresponding value added to the graph under the
|
||
|
// specified name. An application may contain at most one unnamed value of a
|
||
|
// given type, but may contain any number of named values of the same type.
|
||
|
//
|
||
|
// type ConnectionResult struct {
|
||
|
// fx.Out
|
||
|
//
|
||
|
// ReadWrite *sql.DB `name:"rw"`
|
||
|
// ReadOnly *sql.DB `name:"ro"`
|
||
|
// }
|
||
|
//
|
||
|
// func ConnectToDatabase(...) (ConnectionResult, error) {
|
||
|
// // ...
|
||
|
// return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil
|
||
|
// }
|
||
|
//
|
||
|
// # Value Groups
|
||
|
//
|
||
|
// To make it easier to produce and consume many values of the same type, Fx
|
||
|
// supports named, unordered collections called value groups. For details on
|
||
|
// consuming value groups, see the documentation for the In type.
|
||
|
//
|
||
|
// Constructors can send values into value groups by returning a result struct
|
||
|
// tagged with `group:".."`.
|
||
|
//
|
||
|
// type HandlerResult struct {
|
||
|
// fx.Out
|
||
|
//
|
||
|
// Handler Handler `group:"server"`
|
||
|
// }
|
||
|
//
|
||
|
// func NewHelloHandler() HandlerResult {
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// func NewEchoHandler() HandlerResult {
|
||
|
// // ...
|
||
|
// }
|
||
|
//
|
||
|
// Any number of constructors may provide values to this named collection, but
|
||
|
// the ordering of the final collection is unspecified. Keep in mind that
|
||
|
// value groups require parameter and result structs to use fields with
|
||
|
// different types: if a group of constructors each returns type T, parameter
|
||
|
// structs consuming the group must use a field of type []T.
|
||
|
//
|
||
|
// To provide multiple values for a group from a result struct, produce a
|
||
|
// slice and use the `,flatten` option on the group tag. This indicates that
|
||
|
// each element in the slice should be injected into the group individually.
|
||
|
//
|
||
|
// type IntResult struct {
|
||
|
// fx.Out
|
||
|
//
|
||
|
// Handler []int `group:"server"` // Consume as [][]int
|
||
|
// Handler []int `group:"server,flatten"` // Consume as []int
|
||
|
// }
|
||
|
type Out = dig.Out
|