di

package module
v0.0.7 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2024 License: MIT Imports: 9 Imported by: 0

README

go-path/di

This is The Way to do Dependency injection in Go.

Don't know what DI is? Wikipedia

Show me the code!

Source: https://github.com/go-path/di/tree/main/examples/basic

package padawan

import "github.com/go-path/di"

type JediService interface {
	FeelTheForce()
}

type PadawanController struct {
	Master JediService `inject:""`
}

func (p *PadawanController) Initialize() {
	println("[Padawan] Master, I want to learn the ways of the force...")
	p.Master.FeelTheForce()
}

func init() {
	// register as startup component, injecting dependencies
	di.Injected[*PadawanController](di.Startup(100))
}
package jedi

import "github.com/go-path/di"

type yodaServiceImp struct{}

func (s *yodaServiceImp) FeelTheForce() {
	println("[Yoda] Patience You Must Have My Young Padawan")
}

func init() {
	di.Register(&yodaServiceImp{})
}
package main

import (
	"github.com/go-path/di"

	_ "di/example/basic/jedi"
	_ "di/example/basic/padawan"
)

func main() {
	di.Initialize()
}

output

INFO [di] '*jedi.yodaServiceImp' is a candidate for 'padawan.JediService'
[Padawan] Master, I want to learn the ways of the force...
[Yoda] Patience You Must Have My Young Padawan

Features

  • Typesafe: Using generics.
  • Extensible: And configurable.
  • Ergonomic: Easy to understand. Focused on providing a simple yet efficient API.
  • Predictable: The programmer has control over dependencies and can define qualifiers.
  • Framework Agnostic: It can be used with any library.

Goal

To be a lightweight, simple, ergonomic and high-performance DI container that can be easily extended. A foundation for the development of more complex frameworks and architectural structures.

Usage

Read our Full Documentation to learn how to use go-path/di.

Warning

ARE YOU ALLERGIC?

Some concepts applied in this library may contain gluten and/or be inspired by the implementations of CDI Java and Spring IoC.

And obviously, this implementation uses reflection, which for some uninformed individuals can be detrimental. It is important to mention that we thoroughly sanitize our hands before handling any Type or Value.

We will not be held responsible if you become a productive developer after coming into contact with any part of go-path/di.

"Your path you must decide.” — Yoda

Get involved

All kinds of contributions are welcome!

🐛 Found a bug?
Let me know by creating an issue.

Have a question?
Discussions is a good place to start.

⚙️ Interested in fixing a bug or adding a feature?
Check out the contributing guidelines.

📖 Can we improve our documentation?
Pull requests even for small changes can be helpful. Each page in the docs can be edited by clicking the "Edit on GitHub" link at the bottom right.

License

This code is distributed under the terms and conditions of the MIT license.

Documentation

Overview

Exports the methods of a global instance to simplify the use of the lib

Index

Constants

View Source
const (
	SCOPE_SINGLETON string = "singleton"
	SCOPE_PROTOTYPE string = "prototype"
)

Variables

View Source
var (
	ErrCycleDetected         = errors.New("this component introduces a cycle")
	ErrManyCandidates        = errors.New("multiple candidates found")
	ErrContainerLocked       = errors.New("container is locked")
	ErrContextRequired       = errors.New("context required")
	ErrInvalidProvider       = errors.New("invalid provider")
	ErrMissingDependency     = errors.New("missing dependencies")
	ErrCandidateNotFound     = errors.New("no candidate found")
	ErrNoScopeNameDefined    = errors.New("no scope name defined for component")
	ErrCurrentlyInCreation   = errors.New("requested component is currently in creation")
	ErrNoScopeNameRegistered = errors.New("no Scope registered")
)
View Source
var ErrNotStruct = errors.New("the Injected method only accepts struct or *struct")

Functions

func AllOf

func AllOf[T any](c Container, ctx context.Context) (o []T, e error)

func AllOfFilter

func AllOfFilter[T any](filter *FilteredFactories, ctx context.Context) (o []T, e error)

func Alternative

func Alternative(f *Factory)

Alternative indicates that a component should NOT be given preference when multiple candidates are qualified to inject a single-valued dependency.

If exactly one NON-ALTERNATIVE component exists among the candidates, it will be the injected value.

Example:

di.Register(func(repository FooRepository) FooService {
	return &FooServiceImpl{ repository: repository }
})

di.Register(func() FooRepository {
	return &MemoryRepositoryImpl{}
})

di.Register(func() FooRepository {
	return &DatabaseRepositoryImpl{}
}, di.Alternative)

Because DatabaseRepositoryImpl is marked with Alternative, it will NOT be injected over the MemoryRepositoryImpl variant assuming both are present as component within the same di container.

func Contains

func Contains(key reflect.Type) bool

Contains check if this container contains a component with the given key. Does not consider any hierarchy this container may participate in.

func ContainsRecursive

func ContainsRecursive(key reflect.Type) bool

func DefaultFactorySortLessFn

func DefaultFactorySortLessFn(a, b *Factory) bool

DefaultFactorySortLessFn is the default sorting function used by the Filter method, which sorts the factories in the following order:

1) Mock (test) 2) Primary 3) NOT Alternative 4) Lower Order

func Destroy

func Destroy() error

Destroy this container

func DestroyObject

func DestroyObject(key reflect.Type, object any) error

DestroyObject destroy the given instance

func DestroySingletons

func DestroySingletons() error

DestroySingletons destroy all singleton components in this container. To be called on shutdown of a factory.

func Get

func Get[T any](ctx ...context.Context) (o T, e error)

Get return an instance, which may be shared or independent, of the specified component.

func GetFrom

func GetFrom[T any](c Container, contexts ...context.Context) (o T, e error)

GetFrom get a instance from container using generics (returns error)

func Initialize

func Initialize(ctx ...context.Context) error

Initialize initialize all non-lazy singletons (startup)

func Injected

func Injected[T any](opts ...FactoryConfig)

Injector simplifies component registration through reflection.

Example:

type myController struct {
	MyService Service `inject:""`
}

di.Injected[*myController]()

In the example above, the MyService dependency will be injected automatically.

func InjectedTo added in v0.0.6

func InjectedTo[T any](c Container, opts ...FactoryConfig)

Injector simplifies component registration through reflection.

Example:

type myController struct {
	MyService Service `inject:""`
}

di.InjectedTo[*myController](di.Global())

In the example above, the MyService dependency will be injected automatically.

func Injector added in v0.0.6

func Injector[T any]() func(Container, context.Context) (out T, err error)

Injector simplifies component registration through reflection.

Example:

type myController struct {
	MyService Service `inject:""`
}

di.Register(di.Injector[*myController]())

In the example above, the MyService dependency will be injected automatically.

@TODO: embedded structs

func Key

func Key[T any]() reflect.Type

Key is a pointer to a type

func KeyOf

func KeyOf(t any) reflect.Type

KeyOf get a key for a value

func Mock

func Mock(mock any) (cleanup func())

Mock test only, register a mock instance to the container

func MustGetFrom

func MustGetFrom[T any](c Container, ctx ...context.Context) T

MustGetFrom get a instance from container using generics (panic on error)

func Primary

func Primary(f *Factory)

Primary indicates that a component should be given preference when multiple candidates are qualified to inject a single-valued dependency. If exactly one 'primary' component exists among the candidates, it will be the injected value.

Example:

di.Register(func(repository FooRepository) FooService {
	return &FooServiceImpl{ repository: repository }
})

di.Register(func() FooRepository {
	return &MemoryRepositoryImpl{}
})

di.Register(func() FooRepository {
	return &DatabaseRepositoryImpl{}
}, di.Primary)

Because DatabaseRepositoryImpl is marked with Primary, it will be injected preferentially over the MemoryRepositoryImpl variant assuming both are present as component within the same di container.

func Prototype

func Prototype(f *Factory)

Prototype identifies a component that a new instance is created every time the component factory is invoked.

Example:

di.Register(func() MyService {
	return &MyServiceImpl{ Id: uuid.New() }
}, di.Prototype)

di.Register(func(s MyService) MyControllerA {
	print(s.Id) // first uuid
})

di.Register(func(s MyService, ctn di.Container, ctx context.Context) MyControllerB {
	print(s.Id) // second uuid

	s2, _ := di.Get[testService](ctn, ctx)
	print(s2.Id) // third uuid
})

func Register

func Register(ctor any, opts ...FactoryConfig)

func RegisterScope

func RegisterScope(name string, scope ScopeI) error

RegisterScope Register the given scope, backed by the given ScopeI implementation.

func ResolveArgs

func ResolveArgs(factory *Factory, ctx ...context.Context) ([]reflect.Value, error)

ResolveArgs returns an ordered list of values which may be passed directly to the Factory Create method

func ShouldRegister

func ShouldRegister(ctor any, opts ...FactoryConfig) error

func Singleton

func Singleton(f *Factory)

Singleton identifies a component that only instantiates once.

Example:

di.Register(func() MyService {
	return &MyServiceImpl{ Id: uuid.New() }
}, di.Singleton)

di.Register(func(s MyService) MyControllerA {
	print(s.Id) // uuid value
})

di.Register(func(s MyService) MyControllerB {
	print(s.Id) // same uuid value
})

Types

type AlternativeQualifier

type AlternativeQualifier uint8

type Callback

type Callback func(any)

type ConditionFunc

type ConditionFunc func(Container, *Factory) bool

type Container

type Container interface {

	// Initialize initialize all non-lazy singletons (startup)
	Initialize(ctx ...context.Context) error

	Register(ctor any, opts ...FactoryConfig)

	ShouldRegister(ctor any, opts ...FactoryConfig) error

	// RegisterScope Register the given scope, backed by the given ScopeI implementation.
	RegisterScope(name string, scope ScopeI) error

	// Get return an instance, which may be shared or independent, of the specified component.
	Get(key reflect.Type, ctx ...context.Context) (any, error)

	// Contains check if this container contains a component with the given key.
	// Does not consider any hierarchy this container may participate in.
	Contains(key reflect.Type) bool

	ContainsRecursive(key reflect.Type) bool

	Filter(options ...FactoryConfig) *FilteredFactories

	GetObjectFactory(factory *Factory, managed bool, ctx ...context.Context) CreateObjectFunc

	GetObjectFactoryFor(key reflect.Type, managed bool, ctx ...context.Context) CreateObjectFunc

	// ResolveArgs returns an ordered list of values which may be passed directly to the Factory Create method
	ResolveArgs(factory *Factory, ctx ...context.Context) ([]reflect.Value, error)

	// Destroy this container
	Destroy() error

	// DestroyObject destroy the given instance
	DestroyObject(key reflect.Type, object any) error

	// DestroySingletons destroy all singleton components in this container. To be called on shutdown of a factory.
	DestroySingletons() error

	// Mock test only, register a mock instance to the container
	Mock(mock any) (cleanup func())
}

func Global

func Global() Container

func New

func New(parent Container) Container

type CreateObjectFunc

type CreateObjectFunc func() (any, DisposableAdapter, error)

func GetObjectFactory

func GetObjectFactory(factory *Factory, managed bool, ctx ...context.Context) CreateObjectFunc

func GetObjectFactoryFor

func GetObjectFactoryFor(key reflect.Type, managed bool, ctx ...context.Context) CreateObjectFunc

type Disposable

type Disposable interface {
	// Destroy invoked by the container on destruction of a component.
	Destroy()
}

Disposable interface to be implemented by components that want to release resources on destruction

See Disposer

type DisposableAdapter

type DisposableAdapter interface {
	Context() context.Context
	Dispose()
}

DisposableAdapter adapter that perform various destruction steps on a given component instance:

type Factory

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

Factory is a node in the dependency graph that represents a constructor provided by the user and the basic attributes of the returned component (if applicable)

func (*Factory) Alternative

func (f *Factory) Alternative() bool

Alternative returns true if this is a Alternative candidate for a component (has the qualifier AlternativeQualifier)

func (*Factory) Conditions

func (f *Factory) Conditions() []ConditionFunc

Conditions returns the list of conditions methods for this factory The component is only eligible for registration when all specified conditions match.

func (*Factory) Constructor

func (f *Factory) Constructor() reflect.Value

func (*Factory) Create

func (f *Factory) Create(args []reflect.Value) (any, error)

Create a new instance of component.

func (*Factory) Disposers

func (f *Factory) Disposers() []Callback

Disposers returns the list of disposer methods for this factory

func (*Factory) HasConditions

func (f *Factory) HasConditions() bool

HasConditions returns true if this factory has any condition method

func (*Factory) HasDisposers

func (f *Factory) HasDisposers() bool

HasDisposers returns true if this factory has any disposer method

func (*Factory) HasQualifier

func (f *Factory) HasQualifier(q reflect.Type) bool

HasQualifier return true if this Factory has the specified qualifier

func (*Factory) Id added in v0.0.5

func (f *Factory) Id() int

Id gets the factory id

func (*Factory) IsTrueSingleton

func (f *Factory) IsTrueSingleton() bool

IsTrueSingleton returns true if is a reference for a singleton instance

func (*Factory) Key

func (f *Factory) Key() reflect.Type

Key gets the factory component Key (Key = reflect.TypeOf(ComponentType))

func (*Factory) Mock

func (f *Factory) Mock() bool

Mock returns true if this is a Mock factory (testing)

func (*Factory) Name added in v0.0.7

func (f *Factory) Name() string

human readable name

func (*Factory) Order

func (f *Factory) Order() int

Order the order value of this factory

Higher values are interpreted as lower priority. As a consequence, the object with the lowest value has the highest priority.

Same order values will result in arbitrary sort positions for the affected objects.

func (*Factory) Params

func (f *Factory) Params() []reflect.Type

func (*Factory) Primary

func (f *Factory) Primary() bool

Primary returns true if this is a Primary candidate for a component (has the qualifier PrimaryQualifier)

func (*Factory) Prototype

func (f *Factory) Prototype() bool

Prototype returns true if the scope = 'prototype'

func (*Factory) Qualifiers

func (f *Factory) Qualifiers() []reflect.Type

Qualifiers returns the list of qualifiers for this factory

func (*Factory) ReturnErrorIdx

func (f *Factory) ReturnErrorIdx() int

func (*Factory) ReturnValueIdx

func (f *Factory) ReturnValueIdx() int

func (*Factory) ReturnsError

func (f *Factory) ReturnsError() bool

func (*Factory) ReturnsValue

func (f *Factory) ReturnsValue() bool

func (*Factory) Scope

func (f *Factory) Scope() string

Scope gets the scope name

func (*Factory) Singleton

func (f *Factory) Singleton() bool

Singleton returns true if the scope = 'singleton'

func (*Factory) Startup

func (f *Factory) Startup() bool

Startup returns true if this factory is configured to run during the container initialization

func (*Factory) Type

func (f *Factory) Type() reflect.Type

type FactoryConfig

type FactoryConfig func(*Factory)

FactoryConfig is the type to configure the component Factory. Container.Register accepts any number of config (this is functional option pattern).

func Condition

func Condition(condition ConditionFunc) FactoryConfig

Condition a single condition that must be matched in order for a component to be registered. Conditions are checked immediately before the component factory is due to be registered and are free to veto registration based on any criteria that can be determined at that point.

func Disposer

func Disposer[T any](disposer func(T)) FactoryConfig

Disposer register a disposal function to the component. A factory component may declare multiple disposer methods. If the factory returns nil, the disposer will be ignored

See Disposable

func Initializer

func Initializer[T any](initializer func(T)) FactoryConfig

Initializer register a initializer function to the component. A factory component may declare multiple initializers methods. If the factory returns nil, the initializer will be ignored

See Initializable

func Order

func Order(order int) FactoryConfig

Order can be applied to any component to indicate in what order they should be used.

Higher values are interpreted as lower priority. As a consequence, the object with the lowest value has the highest priority.

Same order values will result in arbitrary sort positions for the affected objects.

If the component is marked as Startup, the order determines its execution order.

Order is also used during dependency injection. The candidate with the lower order will be injected.

A framework can implement filters and use order to define the order of execution

func Qualify

func Qualify[Q any]() FactoryConfig

Qualify register a qualifier for the component. Anyone can define a new qualifier.

Example:

type MyQualifier string

di.Register(func() *MyService {
	return &MyService{}
}, di.Qualify[testQualifier]())

func Scoped

func Scoped(scope string) FactoryConfig

Scoped identifies the lifecycle of an instance, such as singleton, prototype, and so forth.. A scope governs how the container reuses instances of the type.

To register additional custom scopes, see Container.RegisterScope.

Defaults to an empty string ("") which implies SCOPE_SINGLETON.

func Startup

func Startup(order int) FactoryConfig

Startup indicates that this component must be initialized during container initialization (Container.Initialize method)

Example:

di.Register(func()  {
	print("Second")
}, Startup(200))

di.Register(func()  {
	print("First")
}, Startup(100))

func Stereotype

func Stereotype(options ...FactoryConfig) FactoryConfig

Stereotype a stereotype encapsulates any combination of ComponentOption

Example:

var Controller = di.Stereotype(di.Singleton, di.Qualify[testQualifier](), di.Startup(500))

di.Register(func() MyController {
	return &MyController{}
}, Controller)

Example: Filter using Stereotype

di.Filter(Controller).Foreach(func(f *Factory) (bool, error) { ... })

type FilteredFactories

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

func Filter

func Filter(options ...FactoryConfig) *FilteredFactories

func FilterOf

func FilterOf[T any](c Container) *FilteredFactories

func (*FilteredFactories) Foreach

func (f *FilteredFactories) Foreach(visitor func(f *Factory) (stop bool, err error)) error

func (*FilteredFactories) Instances

func (f *FilteredFactories) Instances(ctx context.Context) ([]any, error)

func (*FilteredFactories) Sort

func (f *FilteredFactories) Sort(less func(a, b *Factory) bool) *FilteredFactories

type Initializable

type Initializable interface {
	// Initialize invoked by the container on creation of a component.
	Initialize()
}

Initializable interface to be implemented by components that want to initialize resources on creation

See Initializer

type IntectorFn added in v0.0.7

type IntectorFn func(Container, context.Context) (out any, err error)

func InjectorOf added in v0.0.7

func InjectorOf(structType reflect.Type) IntectorFn

type Parameter

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

Parameter representação de um parametro usado na injeção de dependencias

func (*Parameter) Candidates

func (p *Parameter) Candidates() (list []*Factory)

Candidates list alternative matches (Ex. value = A, B implements A, B is candidate, if A is missing)

func (*Parameter) Factories

func (p *Parameter) Factories() (list []*Factory)

Factories list all candidates that exactly matches the type

func (*Parameter) HasCandidates

func (p *Parameter) HasCandidates() bool

HasCandidates checks if there is any candidate for this parameter

func (*Parameter) IsValidCandidate

func (p *Parameter) IsValidCandidate(f *Factory) (isCandidate bool, isExactMatch bool)

func (*Parameter) Key

func (p *Parameter) Key() reflect.Type

func (*Parameter) Provider

func (p *Parameter) Provider() bool

Provider indicates that this parameter is a provider (Ex. func(sq Provider[*testService])

func (*Parameter) Qualified

func (p *Parameter) Qualified() bool

Qualified indicates that this parameter is qualified (Ex. func(sq Qualified[*MyService, MyQualifier])

func (*Parameter) Qualifier

func (p *Parameter) Qualifier() reflect.Type

func (*Parameter) Unmanaged

func (p *Parameter) Unmanaged() bool

Unmanaged indicates that this parameter is a unmanaged provider (Ex. func(sq Unmanaged[*testService])

func (*Parameter) Value

func (p *Parameter) Value() reflect.Type

func (*Parameter) ValueOf

func (p *Parameter) ValueOf(value any) reflect.Value

type PrimaryQualifier

type PrimaryQualifier uint8

type Provider

type Provider[T any] struct {
	TypeBase[T]
	// contains filtered or unexported fields
}

Provider provides instances of T. For any type T that can be injected, you can also inject Provider<T>. Compared to injecting T directly, injecting Provider<T> enables:

  • lazy or optional retrieval of an instance.
  • breaking circular dependencies.

Example:

di.Register(func(sp di.Provider[testService]) {
	if condition {
		service, err := sp.Get()
	}
})

See Unmanaged

func (Provider[T]) Get

func (p Provider[T]) Get() (o T, e error)

Get the value

func (Provider[T]) With

func (p Provider[T]) With(supplier func() (any, error)) Provider[T]

type Qualified

type Qualified[T any, Q any] struct {
	TypeBase[T]
	// contains filtered or unexported fields
}

Qualified allows you to inject a dependency that has a qualifier

Example:

type MyQualifier string

di.Register(func(sq Qualified[*MyService, MyQualifier]) {
	myService := sq.Get()
})

func (Qualified[T, Q]) Get

func (q Qualified[T, Q]) Get() T

Get the value

func (Qualified[T, Q]) Qualifier

func (q Qualified[T, Q]) Qualifier() reflect.Type

Qualifier get the qualifier type

func (Qualified[T, Q]) With

func (q Qualified[T, Q]) With(value any) Qualified[T, Q]

WithValue create a new instance of Qualified with the value

type ScopeI

type ScopeI interface {
	// Get return the object with the given Factory from the underlying scope,
	// creating it if not found in the underlying storage mechanism.
	//
	// If CreateObjectFunc returns a disposer, the scope need to register a callback to
	// be executed on destruction of the specified object in the scope (or at
	// destruction of the entire scope, if the scope does not destroy individual
	//	objects but rather only terminates in its entirety).
	Get(context.Context, *Factory, CreateObjectFunc) (any, error)

	// Remove the object with the given Factory from the underlying scope.
	// Returns nil if no object was found; otherwise returns the removed Object.
	Remove(*Factory, any) (any, error)

	Destroy()
}

type TypeBase

type TypeBase[T any] struct {
}

func (TypeBase[T]) Type

func (t TypeBase[T]) Type() reflect.Type

Type get the type of component

type Unmanaged

type Unmanaged[T any] struct {
	TypeBase[T]
	// contains filtered or unexported fields
}

Unmanaged Provider allow the creation of unmanaged instances. The container will not keep track of these instances and the application will be responsible for cleaning them up.

Example:

di.Register(func(up di.Unmanaged[*Seat]) {
	driver, err := sp.Get()
	passenger, err := sp.Get()
})

See Provider

func (Unmanaged[T]) Get

func (u Unmanaged[T]) Get() (o T, a DisposableAdapter, e error)

Get the value

func (Unmanaged[T]) Unmanaged

func (u Unmanaged[T]) Unmanaged() bool

func (Unmanaged[T]) With

func (u Unmanaged[T]) With(supplier func() (any, DisposableAdapter, error)) Unmanaged[T]

Jump to

Keyboard shortcuts

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