di

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2021 License: MIT Imports: 5 Imported by: 1

README

CircleCI codecov Go Report Card GoDoc

Dependency injection

DI is a dependency injection library that is focused on clean API and flexibility. DI has two types of top-level abstractions: Container and Resolver. First one is responsible for accepting constructors and implementations and creating abstraction bindings out of them. Second implements different implementation resolution scenarios against one or more Containers.

Initially this library was heavily inspired by GoLobby Container but since then had a lot of backwards incompatible changes in structure, functionality and API. To install DI simply run in your project directory:

go get github.com/HnH/di
Constructor

Constructor implements a Construct() method which is called either after binding to container in case of singleton, either after factory method was called.

type Constructor interface {
    Construct() error
}
Container
type Container interface {
    Singleton(constructor interface{}, opts ...Option) error
    Factory(constructor interface{}, opts ...Option) error
    Implementation(implementation interface{}, opts ...Option) error
    ListBindings(reflect.Type) (map[string]Binding, error)
    Reset()
}
Singleton

Singleton() method requires a constructor which will return Implementation(s) of Abstraction(s). Constructor will be called once and returned Implementations(s) will later always bound to Abstraction(s) on resolution requests.

err = di.Singleton(func() (Abstraction, SecondAbstraction) {
    return Implementation, SecondImplementation
})

// Singleton may also accept naming option which means that returned Implementation will be available only under provided name.
err = di.Singleton(func() (Abstraction) {
    return Implementation
}, di.WithName("customName"))

// Name can be provided for each of the Implementations if there are more than one.
err = di.Singleton(func() (Abstraction, SecondAbstraction) {
    return Implementation, SecondImplementation
}, di.WithName("customName", "secondCustomName"))

// If there is only one Implementation returned you may give multiple aliases for it.
err = di.Singleton(func() (Abstraction) {
    return Implementation
}, di.WithName("customName", "secondCustomName"))


// WithFill() option calls `resolver.Fill()` on an instance right after it is created.
err = di.Singleton(func() (Abstraction) {
    return Implementation
}, di.WithFill()) // di.resolver.Fill(Implementation) will be called under the hood
Factory

Factory() method requires a constructor which will return exactly one Implementation of exactly one Abstraction. Constructor will be called on each Abstraction resolution request.

err = di.Factory(func() (Abstraction) {
    return Implementation
})

// Factory also optionally accepts naming option which means that returned Implementation will be available only under provided name.
err := di.Factory(func() (Abstraction) {
    return Implementation
}, di.WithName("customName"))

// Similarly to Singleton binding WithFill() option can be provided
err = di.Factory(func() (Abstraction) {
return Implementation
}, di.WithFill()) // di.resolver.Fill(Implementation) will be called under the hood
Implementation

Implementation() receives ready instance and binds it to its real type, which means that declared abstract variable type (interface) is ignored.

var circle Shape = newCircle()
err = di.Implementation(circle)

// Will return error di: no binding found for: di_test.Shape
var a Shape
err = di.Resolve(&a)

// Will resolve circle.
var c *Circle
err = di.Resolve(&a)

// Also naming options can be used as everywhere.
err = di.Implementation(circle, di.WithName("customName"))
err = di.Resolve(&c, di.WithName("customName"))
Resolver
type Resolver interface {
    With(implementations ...interface{}) Resolver
    Resolve(receiver interface{}, opts ...Option) error
    Call(function interface{}, opts ...Option) error
    Fill(receiver interface{}) error
}
With

With() takes a list of instantiated implementations and tries to use them in resolving scenarios. In the opposite to Container's Implementation() method With() does not put instances into container and does not reflect a type on a binding time. Instead of this it reuses reflect.Type.AssignableTo() method capabilities on abstraction resolution time.

var circle Shape = newCircle()
err = di.Implementation(circle)

// di: no binding found for: di_test.Shape
di.Call(func(s Shape) { return })

// ok
di.With(circle).Call(func(s Shape) { return }))
Resolve

Resolve() requires a receiver (pointer) of an Abstraction and fills it with appropriate Implementation.

var abs Abstraction
err = di.Resolve(&a)

// Resolution can be done with previously registered names as well
err = di.Resolve(&a, di.WithName("customName"))
Call

The Call() executes as function with resolved Implementation as a arguments.

err = di.Call(func(a Abstraction) {
    // `a` will be an implementation of the Abstraction
})

// Returned values can be bound to variables by providing an option.
var db Database
err = di.Call(func(a Abstraction) Database {
    return &MySQL{a}
}, di.WithReturn(&db))
// db == &MySQL{a}
Fill

The Fill() method takes a struct (pointer) and resolves its fields. The example below expresses how the Fill() method works.

err = di.Singleton(func() Mailer { return &someMailer{} })

err = di.Singleton(func() (Database, Database) {
    return &MySQL{}, &Redis{} 
}, di.WithName("data", "cache"))

type App struct {
    mailer Mailer    `di:"type"`
    data   Database  `di:"name"`
    cache  Database  `di:"name"`
    x int
}

var App = App{}
err = container.Fill(&myApp)

// [Typed Bindings]
// `App.mailer` will be an implementation of the Mailer interface

// [Named Bindings]
// `App.data` will be a MySQL implementation of the Database interface
// `App.cache` will be a Redis implementation of the Database interface

// `App.x` will be ignored since it has no `di` tag

Alternatively map[string]Type or []Type can be provided. It will be filled with all available implementations of provided Type.

var list []Shape
container.Fill(&list)

// []Shape{&Rectangle{}, &Circle{}}

var list map[string]Shape
container.Fill(&list)

// map[string]Shape{"square": &Rectangle{}, "rounded": &Circle{}} 

Documentation

Overview

Package di is a dependency injection library that is focused on clean API and flexibility. DI has two types of top-level abstractions: Container and Resolver. First one is responsible for accepting constructors and implementations and creating abstraction bindings out of them. Second implements different implementation resolution scenarios against one or more Containers.

Initially this library was heavily inspired by GoLobby Container (https://github.com/golobby/container) but since then had a lot of backwards incompatible changes in structure, functionality and API.

Index

Constants

View Source
const DefaultBindName = "default"

DefaultBindName is the name that is used in containers by default when binding values.

Variables

This section is empty.

Functions

func Call

func Call(function interface{}, opts ...Option) error

Call takes a function, builds a list of arguments for it from the available bindings, calls it and returns a result.

func Factory

func Factory(constructor interface{}, opts ...Option) error

Factory binds constructor as a factory method of related type.

func Fill

func Fill(receiver interface{}) error

Fill takes a struct and resolves the fields with the tag `di:"..."`. Alternatively map[string]Type or []Type can be provided. It will be filled with all available implementations of provided Type.

func Implementation added in v1.1.1

func Implementation(implementation interface{}, opts ...Option) error

Implementation receives ready instance and binds it to its REAL type, which means that declared abstract variable type (interface) is ignored

func Reset

func Reset()

Reset deletes all the existing bindings and empties the container instance.

func Resolve

func Resolve(receiver interface{}, opts ...Option) error

Resolve takes a receiver and fills it with the related implementation.

func Singleton

func Singleton(constructor interface{}, opts ...Option) error

Singleton binds value(s) returned from constructor as a singleton objects of related types.

Types

type Binding

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

Binding holds either singleton instance or factory method for a binding

type Constructor added in v1.2.0

type Constructor interface {
	Construct() error
}

Constructor implements a `Construct()` method which is called either after binding to container in case of singleton, either after factory method was called.

type Container

type Container interface {
	Singleton(constructor interface{}, opts ...Option) error
	Factory(constructor interface{}, opts ...Option) error
	Implementation(implementation interface{}, opts ...Option) error
	ListBindings(reflect.Type) (map[string]Binding, error)
	Reset()
}

Container is responsible for abstraction binding

func NewContainer

func NewContainer() Container

NewContainer creates a new instance of the Container

type FillingOption added in v1.2.0

type FillingOption interface {
	SetFill(bool)
}

type NamingOption

type NamingOption interface {
	SetName(...string)
}

type Option

type Option func(Options)

func WithFill added in v1.2.0

func WithFill() Option

func WithName

func WithName(names ...string) Option

func WithReturn

func WithReturn(returns ...interface{}) Option

type Options

type Options interface {
	Apply(Option)
}

type Resolver

type Resolver interface {
	With(implementations ...interface{}) Resolver
	Resolve(receiver interface{}, opts ...Option) error
	Call(function interface{}, opts ...Option) error
	Fill(receiver interface{}) error
}

func NewResolver

func NewResolver(containers ...Container) Resolver

func With

func With(implementations ...interface{}) Resolver

With takes a list of instantiated implementations and tries to use them in resolving scenarios

type ReturnOption

type ReturnOption interface {
	SetReturn(...interface{})
}

Jump to

Keyboard shortcuts

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