do

package module
v2.0.0-beta.5 Latest Latest
Warning

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

Go to latest
Published: Feb 11, 2024 License: MIT Imports: 19 Imported by: 30

README

do - Dependency Injection

tag Go Version GoDoc Build Status Go report Coverage License

⚙️ A dependency injection toolkit based on Go 1.18+ Generics.

This library implements the Dependency Injection design pattern. It may replace the uber/dig fantastic package. samber/do uses Go 1.18+ generics instead of reflection and therefore offers a typesafe API.

See also:

  • samber/lo: A Lodash-style Go library based on Go 1.18+ Generics
  • samber/mo: Monads based on Go 1.18+ Generics (Option, Result, Either...)

Why this name?

I love the short name for such a utility library. This name is the sum of DI and Go and no Go package uses this name.

🔥 Migration from v1 to v2

Documentation here

💡 Features

  • 📒 Service registration
    • By type inference
    • By name
  • 🪃 Service invocation
    • Eager loading
    • Lazy loading
    • Transient loading
    • Tag-based invocation
    • Circular dependency detection
  • 🧙‍♂️ Service aliasing
    • Implicit (provide struct, invoke interface)
    • Explicit (provide struct, bind interface, invoke interface)
  • 🔁 Service lifecycle
    • Health check
    • Graceful unload (shutdown)
    • Lifecycle hooks
  • 📦 Scope (a.k.a module) tree
    • Visibility control
    • Dependency grouping
  • 📤 Container
    • Dependency graph resolution and visualization
    • Default container
    • Container cloning
    • Service override
  • 🌈 Lightweight, no dependencies
  • 🔅 No code generation
  • 😷 Typesafe API

🚀 Install

# v2 (latest)
go get github.com/samber/do@v2

# v1
go get github.com/samber/do

This library is v2 and follows SemVer strictly.

No breaking changes will be made to exported APIs before v3.0.0.

This library has no dependencies except the Go std lib.

🤠 Documentation

🛩 Benchmark

// @TODO

🤝 Contributing

Don't hesitate ;)

# Install some dev dependencies
make tools

# Run tests
make test
# or
make watch-test

👤 Contributors

Contributors

💫 Show your support

Give a ⭐️ if this project helped you!

GitHub Sponsors

📝 License

Copyright © 2022 Samuel Berthe.

This project is MIT licensed.

Documentation

Index

Examples

Constants

View Source
const DefaultRootScopeName = "[root]"
View Source
const DefaultStructTagKey = "do"

Variables

View Source
var ErrCircularDependency = errors.New("DI: circular dependency detected")
View Source
var ErrHealthCheckTimeout = errors.New("DI: health check timeout")
View Source
var ErrServiceNotFound = errors.New("DI: could not find service")

Functions

func As

func As[Initial any, Alias any](i Injector) error

As declares an alias for a service.

func AsNamed

func AsNamed[Initial any, Alias any](i Injector, initial string, alias string) error

AsNamed declares a named alias for a named service.

func ExampleInjector_Clone

func ExampleInjector_Clone()

func ExampleInjector_HealthCheck

func ExampleInjector_HealthCheck()

func ExampleInjector_ListInvokedServices_invoked

func ExampleInjector_ListInvokedServices_invoked()

func ExampleInjector_ListInvokedServices_notInvoked

func ExampleInjector_ListInvokedServices_notInvoked()

func ExampleInjector_ListProvidedServices

func ExampleInjector_ListProvidedServices()

func ExampleInjector_Shutdown

func ExampleInjector_Shutdown()

func HealthCheck

func HealthCheck[T any](i Injector) error

HealthCheck returns a service status, using type inference to determine the service name.

func HealthCheckNamed

func HealthCheckNamed(i Injector, name string) error

HealthCheckNamed returns a service status.

func HealthCheckNamedWithContext

func HealthCheckNamedWithContext(ctx context.Context, i Injector, name string) error

HealthCheckNamedWithContext returns a service status.

func HealthCheckWithContext

func HealthCheckWithContext[T any](ctx context.Context, i Injector) error

HealthCheckWithContext returns a service status, using type inference to determine the service name.

func Invoke

func Invoke[T any](i Injector) (T, error)

Invoke invokes a service in the DI container, using type inference to determine the service name.

Example
injector := New()

type test struct {
	foobar string
}

Provide(injector, func(i Injector) (*test, error) {
	return &test{foobar: "foobar"}, nil
})
value, err := Invoke[*test](injector)

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar}
<nil>

func InvokeAs

func InvokeAs[T any](i Injector) (T, error)

InvokeAs invokes a service in the DI container. The first service matching the provided type or interface will be invoked.

func InvokeNamed

func InvokeNamed[T any](i Injector, name string) (T, error)

InvokeNamed invokes a named service in the DI container.

Example
injector := New()

type test struct {
	foobar string
}

ProvideNamed(injector, "my_service", func(i Injector) (*test, error) {
	return &test{foobar: "foobar"}, nil
})
value, err := InvokeNamed[*test](injector, "my_service")

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar}
<nil>

func InvokeStruct

func InvokeStruct[T any](i Injector) (*T, error)

InvokeStruct invokes services located in struct properties. The struct fields must be tagged with `do:""` or `do:"name"`, where `name` is the service name in the DI container. If the service is not found in the DI container, an error is returned. If the service is found but not assignable to the struct field, an error is returned.

func MustAs

func MustAs[Initial any, Alias any](i Injector)

MustAs declares an alias for a service. It panics on error.

func MustAsNamed

func MustAsNamed[Initial any, Alias any](i Injector, initial string, alias string)

AsNamed declares a named alias for a named service. It panics on error.

func MustInvoke

func MustInvoke[T any](i Injector) T

MustInvoke invokes a service in the DI container, using type inference to determine the service name. It panics on error.

Example
injector := New()

type test struct {
	foobar string
}

Provide(injector, func(i Injector) (*test, error) {
	return &test{foobar: "foobar"}, nil
})
value := MustInvoke[*test](injector)

fmt.Println(value)
Output:

&{foobar}

func MustInvokeAs

func MustInvokeAs[T any](i Injector) T

MustInvokeAs invokes a service in the DI container. The first service matching the provided type or interface will be invoked. It panics on error.

func MustInvokeNamed

func MustInvokeNamed[T any](i Injector, name string) T

MustInvokeNamed invokes a named service in the DI container. It panics on error.

Example
injector := New()

type test struct {
	foobar string
}

ProvideNamed(injector, "my_service", func(i Injector) (*test, error) {
	return &test{foobar: "foobar"}, nil
})
value := MustInvokeNamed[*test](injector, "my_service")

fmt.Println(value)
Output:

&{foobar}

func MustInvokeStruct

func MustInvokeStruct[T any](i Injector) *T

InvokeStruct invokes services located in struct properties. The struct fields must be tagged with `do:""` or `do:"name"`, where `name` is the service name in the DI container. If the service is not found in the DI container, an error is returned. If the service is found but not assignable to the struct field, an error is returned. It panics on error.

func MustShutdown

func MustShutdown[T any](i Injector)

MustShutdown stops a service, using type inference to determine the service name. It panics on error.

func MustShutdownNamed

func MustShutdownNamed(i Injector, name string)

MustShutdownNamed stops a named service. It panics on error.

func MustShutdownNamedWithContext

func MustShutdownNamedWithContext(ctx context.Context, i Injector, name string)

MustShutdownNamedWithContext stops a named service. It panics on error.

func MustShutdownWithContext

func MustShutdownWithContext[T any](ctx context.Context, i Injector)

MustShutdownWithContext stops a service, using type inference to determine the service name. It panics on error.

func NameOf

func NameOf[T any]() string

NameOf returns the name of the service in the DI container. This is higly discouraged to use this function, as your code should not declare any dependency explicitly.

func Override

func Override[T any](i Injector, provider Provider[T])

Override replaces the service in the DI container, using type inference to determine the service name.

Example
injector := New()

type test struct {
	foobar string
}

Provide(injector, func(i Injector) (*test, error) {
	return &test{foobar: "foobar1"}, nil
})
Override(injector, func(i Injector) (*test, error) {
	return &test{foobar: "foobar2"}, nil
})
value, err := Invoke[*test](injector)

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar2}
<nil>

func OverrideNamed

func OverrideNamed[T any](i Injector, name string, provider Provider[T])

OverrideNamed replaces the named service in the DI container.

Example
injector := New()

type test struct {
	foobar string
}

ProvideNamed(injector, "my_service", func(i Injector) (*test, error) {
	return &test{foobar: "foobar1"}, nil
})
OverrideNamed(injector, "my_service", func(i Injector) (*test, error) {
	return &test{foobar: "foobar2"}, nil
})
value, err := InvokeNamed[*test](injector, "my_service")

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar2}
<nil>

func OverrideNamedTransient

func OverrideNamedTransient[T any](i Injector, name string, provider Provider[T])

OverrideNamedTransient replaces the named factory in the DI container.

func OverrideNamedValue

func OverrideNamedValue[T any](i Injector, name string, value T)

OverrideNamedValue replaces the named value in the DI container.

Example
injector := New()

type test struct {
	foobar string
}

ProvideNamedValue(injector, "my_service", &test{foobar: "foobar1"})
OverrideNamedValue(injector, "my_service", &test{foobar: "foobar2"})
value, err := InvokeNamed[*test](injector, "my_service")

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar2}
<nil>

func OverrideTransient

func OverrideTransient[T any](i Injector, provider Provider[T])

OverrideTransient replaces the factory in the DI container, using type inference to determine the service name.

func OverrideValue

func OverrideValue[T any](i Injector, value T)

OverrideValue replaces the value in the DI container, using type inference to determine the service name.

func Provide

func Provide[T any](i Injector, provider Provider[T])

Provide registers a service in the DI container, using type inference.

Example
injector := New()

type test struct {
	foobar string
}

Provide(injector, func(i Injector) (*test, error) {
	return &test{foobar: "foobar"}, nil
})
value, err := Invoke[*test](injector)

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar}
<nil>

func ProvideNamed

func ProvideNamed[T any](i Injector, name string, provider Provider[T])

ProvideNamed registers a named service in the DI container.

Example
injector := New()

type test struct {
	foobar string
}

ProvideNamed(injector, "my_service", func(i Injector) (*test, error) {
	return &test{foobar: "foobar"}, nil
})
value, err := InvokeNamed[*test](injector, "my_service")

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar}
<nil>

func ProvideNamedTransient

func ProvideNamedTransient[T any](i Injector, name string, provider Provider[T])

ProvideNamedTransient registers a named factory in the DI container.

func ProvideNamedValue

func ProvideNamedValue[T any](i Injector, name string, value T)

ProvideNamedValue registers a named value in the DI container.

Example
injector := New()

type test struct {
	foobar string
}

ProvideNamedValue(injector, "my_service", &test{foobar: "foobar"})
value, err := InvokeNamed[*test](injector, "my_service")

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar}
<nil>

func ProvideTransient

func ProvideTransient[T any](i Injector, provider Provider[T])

ProvideTransient registers a factory in the DI container, using type inference to determine the service name.

func ProvideValue

func ProvideValue[T any](i Injector, value T)

ProvideValue registers a value in the DI container, using type inference to determine the service name.

Example
injector := New()

type test struct {
	foobar string
}

ProvideValue(injector, &test{foobar: "foobar"})
value, err := Invoke[*test](injector)

fmt.Println(value)
fmt.Println(err)
Output:

&{foobar}
<nil>

func Shutdown

func Shutdown[T any](i Injector) error

Shutdown stops a service, using type inference to determine the service name.

func ShutdownNamed

func ShutdownNamed(i Injector, name string) error

ShutdownNamed stops a named service.

func ShutdownNamedWithContext

func ShutdownNamedWithContext(ctx context.Context, i Injector, name string) error

ShutdownNamedWithContext stops a named service.

func ShutdownWithContext

func ShutdownWithContext[T any](ctx context.Context, i Injector) error

ShutdownWithContext stops a service, using type inference to determine the service name.

Types

type DAG

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

DAG represents a Directed Acyclic Graph of services, tracking dependencies and dependents.

type EdgeService

type EdgeService struct {
	ScopeID   string
	ScopeName string
	Service   string
}

EdgeService represents a service in the DAG, identified by scope ID, scope name, and service name.

type ExplainInjectorOutput

type ExplainInjectorOutput struct {
	ScopeID   string                       `json:"scope_id"`
	ScopeName string                       `json:"scope_name"`
	DAG       []ExplainInjectorScopeOutput `json:"dag"`
}

func ExplainInjector

func ExplainInjector(scope Injector) ExplainInjectorOutput

ExplainInjector returns a human readable description of the injector, with services and scope tree.

func (*ExplainInjectorOutput) String

func (id *ExplainInjectorOutput) String() string

type ExplainInjectorScopeOutput

type ExplainInjectorScopeOutput struct {
	ScopeID   string                         `json:"scope_id"`
	ScopeName string                         `json:"scope_name"`
	Scope     Injector                       `json:"scope"`
	Services  []ExplainInjectorServiceOutput `json:"services"`
	Children  []ExplainInjectorScopeOutput   `json:"children"`

	IsAncestor bool `json:"is_ancestor"`
	IsChildren bool `json:"is_children"`
}

func (*ExplainInjectorScopeOutput) String

func (ids *ExplainInjectorScopeOutput) String() string

type ExplainInjectorServiceOutput

type ExplainInjectorServiceOutput struct {
	ServiceName      string        `json:"service_name"`
	ServiceType      ServiceType   `json:"service_type"`
	ServiceTypeIcon  string        `json:"service_type_icon"`
	ServiceBuildTime time.Duration `json:"service_build_time,omitempty"`
	IsHealthchecker  bool          `json:"is_healthchecker"`
	IsShutdowner     bool          `json:"is_shutdowner"`
}

func (*ExplainInjectorServiceOutput) String

func (idss *ExplainInjectorServiceOutput) String() string

type ExplainServiceDependencyOutput

type ExplainServiceDependencyOutput struct {
	ScopeID   string                           `json:"scope_id"`
	ScopeName string                           `json:"scope_name"`
	Service   string                           `json:"service"`
	Recursive []ExplainServiceDependencyOutput `json:"recursive"`
}

func (*ExplainServiceDependencyOutput) String

func (sdd *ExplainServiceDependencyOutput) String() string

type ExplainServiceOutput

type ExplainServiceOutput struct {
	ScopeID          string                           `json:"scope_id"`
	ScopeName        string                           `json:"scope_name"`
	ServiceName      string                           `json:"service_name"`
	ServiceType      ServiceType                      `json:"service_type"`
	ServiceBuildTime time.Duration                    `json:"service_build_time,omitempty"`
	Invoked          *stacktrace.Frame                `json:"invoked"`
	Dependencies     []ExplainServiceDependencyOutput `json:"dependencies"`
	Dependents       []ExplainServiceDependencyOutput `json:"dependents"`
}

func ExplainNamedService

func ExplainNamedService(scope Injector, name string) (description ExplainServiceOutput, ok bool)

ExplainNamedService returns a human readable description of the service. It returns false if the service is not found. Please call Invoke[T] before ExplainNamedService[T] to ensure that the service is registered.

func ExplainService

func ExplainService[T any](i Injector) (description ExplainServiceOutput, ok bool)

ExplainService returns a human readable description of the service. It returns false if the service is not found. Please call Invoke[T] before ExplainService[T] to ensure that the service is registered.

func (*ExplainServiceOutput) String

func (sd *ExplainServiceOutput) String() string

type Healthchecker

type Healthchecker interface {
	HealthCheck() error
}

type HealthcheckerWithContext

type HealthcheckerWithContext interface {
	HealthCheck(context.Context) error
}

type Injector

type Injector interface {
	// api
	ID() string
	Name() string
	Scope(string) *Scope
	RootScope() *RootScope
	Ancestors() []*Scope
	Children() []*Scope
	ChildByID(string) (*Scope, bool)
	ChildByName(string) (*Scope, bool)
	ListProvidedServices() []EdgeService
	ListInvokedServices() []EdgeService
	HealthCheck() map[string]error
	HealthCheckWithContext(context.Context) map[string]error
	Shutdown() *ShutdownErrors
	ShutdownWithContext(context.Context) *ShutdownErrors
	// contains filtered or unexported methods
}

Injector is a DI container.

type InjectorOpts

type InjectorOpts struct {
	HookAfterRegistration func(scope *Scope, serviceName string)
	HookAfterShutdown     func(scope *Scope, serviceName string)

	Logf func(format string, args ...any)

	HealthCheckParallelism   uint          // default: all jobs are executed in parallel
	HealthCheckGlobalTimeout time.Duration // default: no timeout
	HealthCheckTimeout       time.Duration // default: no timeout

	StructTagKey string
}

type Provider

type Provider[T any] func(Injector) (T, error)

type RootScope

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

RootScope is the first level of scope tree.

var DefaultRootScope *RootScope = New()

func New

func New() *RootScope

New creates a new injector.

func NewWithOpts

func NewWithOpts(opts *InjectorOpts) *RootScope

NewWithOpts creates a new injector with options.

func (*RootScope) Ancestors

func (s *RootScope) Ancestors() []*Scope

func (*RootScope) ChildByID

func (s *RootScope) ChildByID(id string) (*Scope, bool)

func (*RootScope) ChildByName

func (s *RootScope) ChildByName(name string) (*Scope, bool)

func (*RootScope) Children

func (s *RootScope) Children() []*Scope

func (*RootScope) Clone

func (s *RootScope) Clone() *RootScope

Clone clones injector with provided services but not with invoked instances.

func (*RootScope) CloneWithOpts

func (s *RootScope) CloneWithOpts(opts *InjectorOpts) *RootScope

CloneWithOpts clones injector with provided services but not with invoked instances, with options.

func (*RootScope) HealthCheck

func (s *RootScope) HealthCheck() map[string]error

func (*RootScope) HealthCheckWithContext

func (s *RootScope) HealthCheckWithContext(ctx context.Context) map[string]error

func (*RootScope) ID

func (s *RootScope) ID() string

pass through

func (*RootScope) ListInvokedServices

func (s *RootScope) ListInvokedServices() []EdgeService

func (*RootScope) ListProvidedServices

func (s *RootScope) ListProvidedServices() []EdgeService

func (*RootScope) Name

func (s *RootScope) Name() string

func (*RootScope) RootScope

func (s *RootScope) RootScope() *RootScope

func (*RootScope) Scope

func (s *RootScope) Scope(name string) *Scope

func (*RootScope) Shutdown

func (s *RootScope) Shutdown() *ShutdownErrors

func (*RootScope) ShutdownOnSignals

func (s *RootScope) ShutdownOnSignals(signals ...os.Signal) (os.Signal, *ShutdownErrors)

ShutdownOnSignals listens for signals defined in signals parameter in order to graceful stop service. It will block until receiving any of these signal. If no signal is provided in signals parameter, syscall.SIGTERM and os.Interrupt will be added as default signal.

func (*RootScope) ShutdownOnSignalsWithContext

func (s *RootScope) ShutdownOnSignalsWithContext(ctx context.Context, signals ...os.Signal) (os.Signal, *ShutdownErrors)

ShutdownOnSignalsWithContext listens for signals defined in signals parameter in order to graceful stop service. It will block until receiving any of these signal. If no signal is provided in signals parameter, syscall.SIGTERM and os.Interrupt will be added as default signal.

func (*RootScope) ShutdownWithContext

func (s *RootScope) ShutdownWithContext(ctx context.Context) *ShutdownErrors

type Scope

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

Scope is a DI container. It must be created with injector.Scope("name") method.

func (*Scope) Ancestors

func (s *Scope) Ancestors() []*Scope

Ancestors returns the list of parent scopes recursively.

func (*Scope) ChildByID

func (s *Scope) ChildByID(id string) (*Scope, bool)

ChildByID returns the child scope recursively by its ID.

func (*Scope) ChildByName

func (s *Scope) ChildByName(name string) (*Scope, bool)

ChildByName returns the child scope recursively by its name.

func (*Scope) Children

func (s *Scope) Children() []*Scope

Children returns the list of immediate child scopes.

func (*Scope) HealthCheck

func (s *Scope) HealthCheck() map[string]error

HealthCheck returns the healthcheck results of the scope, in a map of service name to error.

func (*Scope) HealthCheckWithContext

func (s *Scope) HealthCheckWithContext(ctx context.Context) map[string]error

HealthCheckWithContext returns the healthcheck results of the scope, in a map of service name to error.

func (*Scope) ID

func (s *Scope) ID() string

ID returns the unique identifier of the scope.

func (*Scope) ListInvokedServices

func (s *Scope) ListInvokedServices() []EdgeService

ListInvokedServices returns the list of services invoked by the scope.

func (*Scope) ListProvidedServices

func (s *Scope) ListProvidedServices() []EdgeService

ListProvidedServices returns the list of services provided by the scope.

func (*Scope) Name

func (s *Scope) Name() string

Name returns the name of the scope.

func (*Scope) RootScope

func (s *Scope) RootScope() *RootScope

RootScope returns the root scope.

func (*Scope) Scope

func (s *Scope) Scope(name string) *Scope

Scope creates a new child scope.

func (*Scope) Shutdown

func (s *Scope) Shutdown() *ShutdownErrors

Shutdown shutdowns the scope and all its children.

func (*Scope) ShutdownWithContext

func (s *Scope) ShutdownWithContext(ctx context.Context) *ShutdownErrors

ShutdownWithContext shutdowns the scope and all its children.

type Service

type Service[T any] interface {
	// contains filtered or unexported methods
}

type ServiceAny

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

Like Service[T] but without the generic type.

type ServiceType

type ServiceType string
const (
	ServiceTypeLazy      ServiceType = "lazy"
	ServiceTypeEager     ServiceType = "eager"
	ServiceTypeTransient ServiceType = "transient"
	ServiceTypeAlias     ServiceType = "alias"
)

type ShutdownErrors

type ShutdownErrors map[EdgeService]error

func (*ShutdownErrors) Add

func (e *ShutdownErrors) Add(scopeID string, scopeName string, serviceName string, err error)

func (ShutdownErrors) Error

func (e ShutdownErrors) Error() string

func (ShutdownErrors) Len

func (e ShutdownErrors) Len() int

type Shutdowner

type Shutdowner interface {
	Shutdown()
}

type ShutdownerWithContext

type ShutdownerWithContext interface {
	Shutdown(context.Context)
}

type ShutdownerWithContextAndError

type ShutdownerWithContextAndError interface {
	Shutdown(context.Context) error
}

type ShutdownerWithError

type ShutdownerWithError interface {
	Shutdown() error
}

Directories

Path Synopsis
examples
dag

Jump to

Keyboard shortcuts

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