di

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2020 License: MIT Imports: 8 Imported by: 0

README

DI Container

Documentation Release Build Status Go Report Card Code Coverage

Dependency injection is one form of the broader technique of inversion of control. It is used to increase modularity of the program and make it extensible.

Contents

Documentation

You can use standard pkg.go.dev and inline code comments or if you do not have experience with auto-wiring libraries as google/wire, uber-go/dig or another start with tutorial.

Install

go get github.com/goava/di

Tutorial

Let's learn to use di by example. We will code a simple application that processes HTTP requests.

The full tutorial code is available here

Provide

To start, we will need to provide way to build for two fundamental types: http.Server and http.ServeMux. Let's create a simple functional constructors that build them:

// NewServer builds a http server with provided mux as handler.
func NewServer(mux *http.ServeMux) *http.Server {
	return &http.Server{
		Handler: mux,
	}
}

// NewServeMux creates a new http serve mux.
func NewServeMux() *http.ServeMux {
	return &http.ServeMux{}
}

Supported constructor signature:

// cleanup and error is a optional
func([dep1, dep2, depN]) (result, [cleanup, error])

Now, we can teach the container to build these types in three style ways:

In preferred functional option style:

// create container
container, err := container.New(
	di.Provide(NewServer),
	di.Provide(NewServeMux),
)
if err != nil {
    // handle error
}
Resolve

Next, we can resolve the built server from the container. For this, define the variable of resolved type and pass variable pointer to Resolve function.

If resolved type not found or the process of building instance cause error.

If no error occurred, we can use the variable as if we had built it yourself.

// declare type variable
var server *http.Server
// resolving
err := container.Resolve(&server)
if err != nil {
	// handle error
}

server.ListenAndServe()

Note that by default, the container creates instances as a singleton. But you can change this behaviour. See Prototypes.

Invoke

As an alternative to resolving we can use Invoke() function of Container. It builds dependencies and call provided function. Invoke function may return optional error.

// StartServer starts the server.
func StartServer(server *http.Server) error {
    return server.ListenAndServe()
}

if err := container.Invoke(StartServer); err != nil {
	// handle error
}

Also you can use di.Invoke() container options for call some initialization code.

container, err := di.New(
	di.Provide(NewServer),
	di.Invoke(StartServer),
)
if err != nil {
    // handle error
}

Container run all invoke functions on compile stage. If one of them failed (return error), compile cause error.

Lazy-loading

Result dependencies will be lazy-loaded. If no one requires a type from the container it will not be constructed.

Interfaces

Container make possible to provide implementation as an interface.

// NewServer creates a http server with provided mux as handler.
func NewServer(handler http.Handler) *http.Server {
	return &http.Server{
		Handler: handler,
	}
}

For a container to know that as an implementation of http.Handler is necessary to use, we use the option di.As(). The arguments of this option must be a pointer(s) to an interface like new(Endpoint).

This syntax may seem strange, but I have not found a better way to specify the interface.

Updated container initialization code:

container, err := di.New(
	// provide http server
	di.Provide(NewServer),
	// provide http serve mux as http.Handler interface
	di.Provide(NewServeMux, di.As(new(http.Handler)))
)
if err != nil {
    // handle error
}

Now container uses provide *http.ServeMux as http.Handler in server constructor. Using interfaces contributes to writing more testable code.

Groups

Container automatically groups same types to []<type> slice. It works with di.As(). For example, provide with di.As(new(http.Handler) automatically creates a group []http.Handler.

Let's add some http controllers using this feature. Controllers have typical behavior. It is registering routes. At first, will create an interface for it.

// Controller is an interface that can register its routes.
type Controller interface {
	RegisterRoutes(mux *http.ServeMux)
}

Now we will write controllers and implement Controller interface.

OrderController
// OrderController is a http controller for orders.
type OrderController struct {}

// NewOrderController creates a auth http controller.
func NewOrderController() *OrderController {
	return &OrderController{}
}

// RegisterRoutes is a Controller interface implementation.
func (a *OrderController) RegisterRoutes(mux *http.ServeMux) {
	mux.HandleFunc("/orders", a.RetrieveOrders)
}

// RetrieveOrders loads orders and writes it to the writer.
func (a *OrderController) RetrieveOrders(writer http.ResponseWriter, request *http.Request) {
	// implementation
}
UserController
// UserController is a http endpoint for a user.
type UserController struct {}

// NewUserController creates a user http endpoint.
func NewUserController() *UserController {
	return &UserController{}
}

// RegisterRoutes is a Controller interface implementation.
func (e *UserController) RegisterRoutes(mux *http.ServeMux) {
	mux.HandleFunc("/users", e.RetrieveUsers)
}

// RetrieveUsers loads users and writes it using the writer.
func (e *UserController) RetrieveUsers(writer http.ResponseWriter, request *http.Request) {
    // implementation
}

Just like in the example with interfaces, we will use di.As() provide option.

container, err := di.New(
	di.Provide(NewServer),        // provide http server
	di.Provide(NewServeMux),       // provide http serve mux
	// endpoints
	di.Provide(NewOrderController, di.As(new(Controller))),  // provide order controller
	di.Provide(NewUserController, di.As(new(Controller))),  // provide user controller
)
if err != nil {
    // handle error
}

Now, we can use []Controller group in our mux. See updated code:

// NewServeMux creates a new http serve mux.
func NewServeMux(controllers []Controller) *http.ServeMux {
	mux := &http.ServeMux{}

	for _, controller := range controllers {
		controller.RegisterRoutes(mux)
	}

	return mux
}

The full tutorial code is available here

Advanced features

Modules

With container option Options() you can group your functionality:

// account module
account := di.Options(
    di.Provide(NewAccountController), 
    di.Provide(NewAccountRepository),
)
// auth module
auth := di.Options(
    di.Provide(NewAuthController), 
    di.Provide(NewAuthRepository),
)
// build container
container, err := di.New(
    account, 
    auth,
)
if err != nil {
 // handle error
}
Named definitions

In some cases you have more than one instance of one type. For example two instances of database: master - for writing, slave - for reading.

First way is a wrapping types:

// MasterDatabase provide write database access.
type MasterDatabase struct {
	*Database
}

// SlaveDatabase provide read database access.
type SlaveDatabase struct {
	*Database
}

Second way is a using named definitions with di.WithName() provide option:

// provide master database
di.Provide(NewMasterDatabase, di.WithName("master"))
// provide slave database
di.Provide(NewSlaveDatabase, di.WithName("slave"))

If you need to resolve it from container use di.Name() resolve option.

var db *Database
container.Resolve(&db, di.Name("master"))

If you need to provide named definition in another constructor embed di.Inject.

// ServiceParameters
type ServiceParameters struct {
	di.Inject
	
	// use `di` tag for the container to know that field need to be injected.
	MasterDatabase *Database `di:"master"`
	SlaveDatabase *Database  `di:"slave"`
}

// NewService creates new service with provided parameters.
func NewService(parameters ServiceParameters) *Service {
	return &Service{
		MasterDatabase:  parameters.MasterDatabase,
		SlaveDatabase: parameters.SlaveDatabase,
	}
}
Optional parameters

Also, di.Inject with tag optional provide ability to skip dependency if it not exists in the container.

// ServiceParameter
type ServiceParameter struct {
	di.Inject
	
	Logger *Logger `di:"" optional:"true"`
}

Constructors that declare dependencies as optional must handle the case of those dependencies being absent.

You can use naming and optional together.

// ServiceParameter
type ServiceParameter struct {
	di.Inject
	
	StdOutLogger *Logger `di:"stdout"`
	FileLogger   *Logger `di:"file" optional:"true"`
}
Fill struct

To avoid constant constructor changes, you can also use di.Inject. Note, that supported only struct pointers as constructing result.

// Controller has some endpoints.
type Controller struct {
    di.Inject

    // fields must be public
    Users   UserService     `di:""`
    Friends FriendsService  `di:""`
}

// NewController creates controller.
func NewController() *Controller {
    return &Controller{}
}

Note, that such a constructor will be incorrect without using di

Prototypes

If you want to create a new instance on each extraction use di.Prototype() provide option.

di.Provide(NewRequestContext, di.Prototype())
Cleanup

If a provider creates a value that needs to be cleaned up, then it can return a closure to clean up the resource.

func NewFile(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
}

After container.Cleanup() call, it iterate over instances and call cleanup function if it exists.

container, err := di.New(
	// ...
    di.Provide(NewFile),
)
if err != nil {
    // handle error
}
// do something
container.Cleanup() // file was closed

Comparison

goava/di google/wire uber-go/dig sarulabs/di
Autowiring
Type groups Struct tag Tag
Interface binding
Named definitions
Functional options API
Optional dependencies
Cleanup
Reflection based

Documentation

Overview

Package di provides opinionated way to connect your application components. Container allows you to inject dependencies into constructors or structures without the need to have specified each argument manually.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CompileOption

type CompileOption interface {
	// contains filtered or unexported methods
}

CompileOption modifies compile behaviour. Deprecated: Compile deprecated: https://github.com/goava/di/pull/9

type Constructor

type Constructor interface{}

Constructor is a function with follow signature:

func NewHTTPServer(addr string, handler http.Handler) (server *http.Server, cleanup func(), err error) {
	server := &http.Server{
		Addr: addr,
	}
	cleanup = func() {
		server.Close()
	}
	return server, cleanup, nil
}

This constructor function teaches container how to build server. Arguments (addr and handler) in this function is a dependencies. They will be resolved automatically when someone needs a server. Constructor may have unlimited count of dependencies, but note that container should know how build each of them. Second result of this function is a optional cleanup callback. It describes that container will do on shutdown. Third result is a optional error. Sometimes our types cannot be constructed.

type Container

type Container struct {
	// contains filtered or unexported fields
}

Container is a dependency injection container.

func New

func New(options ...Option) (_ *Container, err error)

New constructs container with provided options. Example usage (simplified):

Define constructors and invocations:

func NewHTTPServer(mux *http.ServeMux) *http.Server {
	return &http.Server{
		Handler: mux,
	}
}

func NewHTTPServeMux() *http.ServeMux {
	return http.ServeMux{}
}

func StartServer(server *http.Server) error {
	return server.ListenAndServe()
}

Use it with container:

container, err := di.New(
	di.Provide(NewHTTPServer),
	di.Provide(NewHTTPServeMux),
	di.Invoke(StartServer),
)
if err != nil {
	// handle error
}

func (*Container) Cleanup

func (c *Container) Cleanup()

Cleanup runs destructors in reverse order that was been created.

func (*Container) Compile

func (c *Container) Compile(_ ...CompileOption) error

Compile compiles the container. Deprecated: Compile deprecated: https://github.com/goava/di/pull/9

func (*Container) Has added in v1.0.0

func (c *Container) Has(into interface{}, options ...ResolveOption) bool

Has checks that type exists in container, if not it return false.

var server *http.Server
if container.Has(&server) {
	// handle server existence
}

It like Resolve() but doesn't instantiate a type.

func (*Container) Invoke

func (c *Container) Invoke(invocation Invocation, options ...InvokeOption) error

Invoke calls the function fn. It parses function parameters. Looks for it in a container. And invokes function with them. See Invocation for details.

func (*Container) Provide

func (c *Container) Provide(constructor Constructor, options ...ProvideOption) error

Provide provides to container reliable way to build type. The constructor will be invoked lazily on-demand. For more information about constructors see Constructor interface. ProvideOption can add additional behavior to the process of type resolving.

func (*Container) Resolve

func (c *Container) Resolve(into interface{}, options ...ResolveOption) error

Resolve resolves type and fills target pointer.

var server *http.Server
if err := container.Resolve(&server); err != nil {
	// handle error
}

type ErrInvokeFailed added in v1.2.0

type ErrInvokeFailed struct {
	// contains filtered or unexported fields
}

ErrInvokeFailed causes when invoke failed.

func (ErrInvokeFailed) Error added in v1.2.0

func (e ErrInvokeFailed) Error() string

Error returns error string.

type ErrProvideFailed added in v1.2.0

type ErrProvideFailed struct {
	// contains filtered or unexported fields
}

ErrProvideFailed causes when constructor providing failed.

func (ErrProvideFailed) Error added in v1.2.0

func (e ErrProvideFailed) Error() string

Error returns error string.

type ErrResolveFailed added in v1.2.0

type ErrResolveFailed struct {
	// contains filtered or unexported fields
}

ErrResolveFailed causes when type resolve failed.

func (ErrResolveFailed) Error added in v1.2.0

func (e ErrResolveFailed) Error() string

Error returns error string.

type Inject added in v1.1.0

type Inject struct {
	// contains filtered or unexported fields
}

Inject indicates that public fields with special tag type will be injected automatically.

type MyType struct {
	di.Inject

	Server *http.Server `di:""` // will be injected
}

type Interface

type Interface interface{}

Interface is a pointer to interface, like new(http.Handler). Tell container that provided type may be used as interface.

type Invocation

type Invocation interface{}

Invocation is a function whose signature looks like:

func StartServer(server *http.Server) error {
	return server.ListenAndServe()
}

Like a constructor invocation may have unlimited count of arguments and they will be resolved automatically. The invocation can return an optional error. Error will be returned as is.

type InvokeOption

type InvokeOption interface {
	// contains filtered or unexported methods
}

InvokeOption is a functional option interface that modify invoke behaviour.

type InvokeParams

type InvokeParams struct {
	// The function
	Fn interface{}
}

InvokeParams is a invoke parameters.

type Logger added in v1.0.0

type Logger interface {
	Logf(format string, values ...interface{})
}

Logger logs internal container actions. By default it omits logs. You can set logger by Option LogFunc(). Also, you can provide you own logger to container as di.Logger interface. Then container use it for internal logs.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option is a functional option that configures container. If you don't know about functional options, see https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis. Below presented all possible options with their description:

  • di.Provide - provide constructors
  • di.Invoke - add invocations
  • di.Resolve - resolves type

func Invoke

func Invoke(fn Invocation, options ...InvokeOption) Option

Invoke returns container option that registers container invocation. All invocations will be called on di.New() after processing di.Provide() options. See Container.Invoke() for details.

func Options

func Options(options ...Option) Option

Options group together container options.

account := di.Options(
  di.Provide(NewAccountController),
  di.Provide(NewAccountRepository),
)
auth := di.Options(
  di.Provide(NewAuthController),
  di.Provide(NewAuthRepository),
)
container, err := di.New(
  account,
  auth,
)
if err != nil {
  // handle error
}

func Provide

func Provide(constructor Constructor, options ...ProvideOption) Option

Provide returns container option that provides to container reliable way to build type. The constructor will be invoked lazily on-demand. For more information about constructors see Constructor interface. ProvideOption can add additional behavior to the process of type resolving.

func Resolve

func Resolve(target interface{}, options ...ResolveOption) Option

Resolve returns container options that resolves type into target. All resolves will be done on compile stage after call invokes.

func WithCompile added in v1.0.0

func WithCompile() Option

WithCompile ejects compile stage. Deprecated: Compile deprecated: https://github.com/goava/di/pull/9

func WithLogger added in v1.0.0

func WithLogger(logger Logger) Option

WithLogger sets container logger.

type ProvideOption

type ProvideOption interface {
	// contains filtered or unexported methods
}

ProvideOption is a functional option interface that modify provide behaviour. See di.As(), di.WithName().

func As

func As(interfaces ...Interface) ProvideOption

As returns provide option that specifies interfaces for constructor resultant type.

INTERFACE USAGE:

You can provide type as interface and resolve it later without using of direct implementation. This creates less cohesion of code and promotes be more testable.

Create type constructors:

func NewServeMux() *http.ServeMux {
	return &http.ServeMux{}
}

func NewServer(handler *http.Handler) *http.Server {
	return &http.Server{
		Handler: handler,
	}
}

Build container with di.As provide option:

container, err := di.New(
	di.Provide(NewServer),
	di.Provide(NewServeMux, di.As(new(http.Handler)),
)
if err != nil {
	// handle error
}
var server *http.Server
if err := container.Resolve(&http.Server); err != nil {
	// handle error
}

In this example you can see how container inject type *http.ServeMux as http.Handler interface into the server constructor.

GROUP USAGE:

Container automatically creates group for interfaces. For example, you can use type []http.Handler in previous example.

var handlers []http.Handler
if err := container.Resolve(&handlers); err != nil {
	// handle error
}

Container checks that provided type implements interface if not cause compile error.

func Prototype

func Prototype() ProvideOption

Prototype modifies Provide() behavior. By default, each type resolves as a singleton. This option sets that each type resolving creates a new instance of the type.

container, err := di.New(
	Provide(NewHTTPServer, inject.Prototype())
)
if err != nil {
	// handle error
}
var server1, server2 *http.Server
if err := container.Resolve(&server1); err != nil {
	// handle error
}
if err := container.Resolve(&server2); err != nil {
	// handle error
}

func WithName

func WithName(name string) ProvideOption

WithName modifies Provide() behavior. It adds name identity for provided type.

type ProvideParams

type ProvideParams struct {
	Name        string
	Interfaces  []Interface
	IsPrototype bool
}

ProvideParams is a Provide() method options. Name is a unique identifier of type instance. Provider is a constructor function. Interfaces is a interface that implements a provider result type.

type ResolveOption

type ResolveOption interface {
	// contains filtered or unexported methods
}

ResolveOption is a functional option interface that modify resolve behaviour.

func Name

func Name(name string) ResolveOption

Name specifies provider string identity. It needed when you have more than one definition of same type. You can identity type by name.

type ResolveParams

type ResolveParams struct {
	Name string
}

ResolveParams is a resolve parameters.

Directories

Path Synopsis
_examples
tutorial command
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL