di

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Jan 24, 2026 License: MIT Imports: 7 Imported by: 0

README ΒΆ

DI - Dependency Injection

This is a forked of the original Do package with an extension to allow dynamically injecting depdencies to a struct through reflection.

Go Version GoDoc Build Status Go report Coverage License

βš™οΈ A dependency injection toolkit based on Go 1.22+ Generics.

This library implements the Dependency Injection design pattern. It may replace the uber/dig fantastic package in simple Go projects. samber/do uses Go 1.22+ generics and therefore is typesafe.

πŸ’‘ Features

  • Service registration
  • Service invocation
  • Service health check
  • Service shutdown
  • Service lifecycle hooks
  • Named or anonymous services
  • Eagerly or lazily loaded services
  • Dependency graph resolution
  • Default Container
  • Container cloning
  • Service override
  • Lightweight, no dependencies
  • No code generation

πŸš€ Services are loaded in invocation order.

πŸ•΅οΈ Service health can be checked individually or globally. Services implementing di.Healthcheckable interface will be called via di.HealthCheck[type]() or Container.HealthCheck().

πŸ›‘ Services can be shutdowned properly, in back-initialization order. Services implementing di.Shutdownable interface will be called via di.Shutdown[type]() or Container.Shutdown().

Di compared to original Do package

We added a method to allow injecting dependencies dynamically through struct reflection. Declare a service, and add a di:"<name>: tag to the field where you want the container to inject the corresponding dependency.

// main.go
container := New()
repository := newRepository()
redisClient := newRedisClient()
ProvideValue[Repository](container, repository)
ProvideValue[RedisClient](container, redisClient)

// service.go
type service struct {
	Repository  Repository  `di:"repository"`
	RedisClient RedisClient `di:"redisClient"`
}

func newService(
    container *di.Container
) (&service, error) {
    s := service{}
    err := container.Inject(&s)
    return s, err
}

πŸš€ Install

go get github.com/fystack/di@v1

This library is v1 and follows SemVer strictly.

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

This library has no dependencies except the Go std lib.

πŸ’‘ Quick start

You can import do using:

import (
    "github.com/fystack/di"
)

Then instanciate services:

func main() {
    container := di.New()

    // provides CarService
    di.Provide(container, NewCarService)

    // provides EngineService
    di.Provide(container, NewEngineService)

    car := di.MustInvoke[*CarService](container)
    car.Start()
    // prints "car starting"

    di.HealthCheck[EngineService](container)
    // returns "engine broken"

    // container.ShutdownOnSIGTERM()    // will block until receiving sigterm signal
    container.Shutdown()
    // prints "car stopped"
}

Services:

type EngineService interface{}

func NewEngineService(i *di.Container) (EngineService, error) {
    return &engineServiceImplem{}, nil
}

type engineServiceImplem struct {}

// [Optional] Implements di.Healthcheckable.
func (c *engineServiceImplem) HealthCheck() error {
	return fmt.Errorf("engine broken")
}
func NewCarService(i *di.Container) (*CarService, error) {
    engine := di.MustInvoke[EngineService](i)
    car := CarService{Engine: engine}
    return &car, nil
}

type CarService struct {
	Engine EngineService
}

func (c *CarService) Start() {
	println("car starting")
}

// [Optional] Implements di.Shutdownable.
func (c *CarService) Shutdown() error {
	println("car stopped")
	return nil
}

🀠 Spec

GoDoc: https://godoc.org/github.com/fystack/di

Container:

Service registration:

Service invocation:

Service override:

Container (DI container)

Build a container for your components. Container is responsible for building services in the right order, and managing service lifecycle.

container := di.New()

Or use nil as the default Container:

di.Provide(nil, func (i *Container) (int, error) {
    return 42, nil
})

service := di.MustInvoke[int](nil)

You can check health of services implementing func HealthCheck() error.

type DBService struct {
    db *sql.DB
}

func (s *DBService) HealthCheck() error {
    return s.db.Ping()
}

container := di.New()
di.Provide(container, ...)
di.Invoke(container, ...)

statuses := container.HealthCheck()
// map[string]error{
//   "*DBService": nil,
// }

De-initialize all compoments properly. Services implementing func Shutdown() error will be called synchronously in back-initialization order.

type DBService struct {
    db *sql.DB
}

func (s *DBService) Shutdown() error {
    return s.db.Close()
}

container := di.New()
di.Provide(container, ...)
di.Invoke(container, ...)

// shutdown all services in reverse order
container.Shutdown()

List services:

type DBService struct {
    db *sql.DB
}

container := di.New()

di.Provide(container, ...)
println(di.ListProvidedServices())
// output: []string{"*DBService"}

di.Invoke(container, ...)
println(di.ListInvokedServices())
// output: []string{"*DBService"}
Service registration

Services can be registered in multiple way:

  • with implicit name (struct or interface name)
  • with explicit name
  • eagerly
  • lazily

Anonymous service, loaded lazily:

type DBService struct {
    db *sql.DB
}

di.Provide[DBService](container, func(i *container) (*DBService, error) {
    db, err := sql.Open(...)
    if err != nil {
        return nil, err
    }

    return &DBService{db: db}, nil
})

Named service, loaded lazily:

type DBService struct {
    db *sql.DB
}

di.ProvideNamed(container, "dbconn", func(i *container) (*DBService, error) {
    db, err := sql.Open(...)
    if err != nil {
        return nil, err
    }

    return &DBService{db: db}, nil
})

Anonymous service, loaded eagerly:

type Config struct {
    uri string
}

di.ProvideValue[Config](container, Config{uri: "postgres://user:pass@host:5432/db"})

Named service, loaded eagerly:

type Config struct {
    uri string
}

di.ProvideNamedValue(container, "configuration", Config{uri: "postgres://user:pass@host:5432/db"})
Service invocation

Loads anonymous service:

type DBService struct {
    db *sql.DB
}

dbService, err := di.Invoke[DBService](container)

Loads anonymous service or panics if service was not registered:

type DBService struct {
    db *sql.DB
}

dbService := di.MustInvoke[DBService](container)

Loads named service:

config, err := di.InvokeNamed[Config](container, "configuration")

Loads named service or panics if service was not registered:

config := di.MustInvokeNamed[Config](container, "configuration")
Individual service healthcheck

Check health of anonymous service:

type DBService struct {
    db *sql.DB
}

dbService, err := di.Invoke[DBService](container)
err = di.HealthCheck[DBService](container)

Check health of named service:

config, err := di.InvokeNamed[Config](container, "configuration")
err = di.HealthCheckNamed(container, "configuration")
Individual service shutdown

Unloads anonymous service:

type DBService struct {
    db *sql.DB
}

dbService, err := di.Invoke[DBService](container)
err = di.Shutdown[DBService](container)

Unloads anonymous service or panics if service was not registered:

type DBService struct {
    db *sql.DB
}

dbService := di.MustInvoke[DBService](container)
di.MustShutdown[DBService](container)

Unloads named service:

config, err := di.InvokeNamed[Config](container, "configuration")
err = di.ShutdownNamed(container, "configuration")

Unloads named service or panics if service was not registered:

config := di.MustInvokeNamed[Config](container, "configuration")
di.MustShutdownNamed(container, "configuration")
Service override

By default, providing a service twice will panic. Service can be replaced at runtime using di.Override helper.

di.Provide[Vehicle](container, func (i *di.Container) (Vehicle, error) {
    return &CarImplem{}, nil
})

di.Override[Vehicle](container, func (i *di.Container) (Vehicle, error) {
    return &BusImplem{}, nil
})
Hooks

2 lifecycle hooks are available in Containers:

  • After registration
  • After shutdown
container := di.NewWithOpts(&di.ContainerOpts{
    HookAfterRegistration: func(container *di.Container, serviceName string) {
        fmt.Printf("Service registered: %s\n", serviceName)
    },
    HookAfterShutdown: func(container *di.Container, serviceName string) {
        fmt.Printf("Service stopped: %s\n", serviceName)
    },

    Logf: func(format string, args ...any) {
        log.Printf(format, args...)
    },
})
Cloning Container

Cloned Container have same service registrations as it's parent, but it doesn't share invoked service state.

Clones are useful for unit testing by replacing some services to mocks.

var container *di.Container;

func init() {
    di.Provide[Service](container, func (i *di.Container) (Service, error) {
        return &RealService{}, nil
    })
    di.Provide[*App](container, func (i *di.Container) (*App, error) {
        return &App{i.MustInvoke[Service](i)}, nil
    })
}

func TestService(t *testing.T) {
    i := container.Clone()
    defer i.Shutdown()

    // replace Service to MockService
    di.Override[Service](i, func (i *di.Container) (Service, error) {
        return &MockService{}, nil
    }))

    app := di.Invoke[*App](i)
    // do unit testing with mocked service
}

πŸ›© Benchmark

// @TODO

🀝 Contributing

Don't hesitate ;)

With Docker
docker-compose run --rm dev
Without Docker
# 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 ΒΆ

This section is empty.

Variables ΒΆ

View Source
var DefaultContainer = New()

Functions ΒΆ

func HealthCheck ΒΆ

func HealthCheck[T any](i *Container) error

func HealthCheckNamed ΒΆ

func HealthCheckNamed(i *Container, name string) error

func Invoke ΒΆ

func Invoke[T any](i *Container) (T, error)
Example ΒΆ
container := New()

type test struct {
	foobar string
}

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

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

&{foobar}
<nil>

func InvokeNamed ΒΆ

func InvokeNamed[T any](i *Container, name string) (T, error)
Example ΒΆ
container := New()

type test struct {
	foobar string
}

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

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

&{foobar}
<nil>

func MustInvoke ΒΆ

func MustInvoke[T any](i *Container) T
Example ΒΆ
container := New()

type test struct {
	foobar string
}

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

fmt.Println(value)
Output:

&{foobar}

func MustInvokeNamed ΒΆ

func MustInvokeNamed[T any](i *Container, name string) T
Example ΒΆ
container := New()

type test struct {
	foobar string
}

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

fmt.Println(value)
Output:

&{foobar}

func MustShutdown ΒΆ

func MustShutdown[T any](i *Container)

func MustShutdownNamed ΒΆ

func MustShutdownNamed(i *Container, name string)

func Override ΒΆ

func Override[T any](i *Container, provider Provider[T])
Example ΒΆ
container := New()

type test struct {
	foobar string
}

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

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

&{foobar2}
<nil>

func OverrideNamed ΒΆ

func OverrideNamed[T any](i *Container, name string, provider Provider[T])
Example ΒΆ
container := New()

type test struct {
	foobar string
}

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

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

&{foobar2}
<nil>

func OverrideNamedValue ΒΆ

func OverrideNamedValue[T any](i *Container, name string, value T)
Example ΒΆ
Container := New()

type test struct {
	foobar string
}

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

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

&{foobar2}
<nil>

func OverrideValue ΒΆ

func OverrideValue[T any](i *Container, value T)

func Provide ΒΆ

func Provide[T any](i *Container, provider Provider[T])
Example ΒΆ
container := New()

type test struct {
	foobar string
}

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

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

&{foobar}
<nil>

func ProvideNamed ΒΆ

func ProvideNamed[T any](i *Container, name string, provider Provider[T])
Example ΒΆ
container := New()

type test struct {
	foobar string
}

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

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

&{foobar}
<nil>

func ProvideNamedValue ΒΆ

func ProvideNamedValue[T any](i *Container, name string, value T)
Example ΒΆ
Container := New()

type test struct {
	foobar string
}

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

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

&{foobar}
<nil>

func ProvideValue ΒΆ

func ProvideValue[T any](i *Container, value T)
Example ΒΆ
Container := New()

type test struct {
	foobar string
}

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

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

&{foobar}
<nil>

func Shutdown ΒΆ

func Shutdown[T any](i *Container) error

func ShutdownNamed ΒΆ

func ShutdownNamed(i *Container, name string) error

Types ΒΆ

type Container ΒΆ

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

func New ΒΆ

func New() *Container
Example ΒΆ
Container := New()

ProvideNamedValue(Container, "PG_URI", "postgres://user:pass@host:5432/db")
uri, err := InvokeNamed[string](Container, "PG_URI")

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

postgres://user:pass@host:5432/db
<nil>

func NewWithOpts ΒΆ

func NewWithOpts(opts *ContainerOpts) *Container
Example ΒΆ
container := NewWithOpts(&ContainerOpts{
	HookAfterShutdown: func(container *Container, serviceName string) {
		fmt.Printf("service shutdown: %s\n", serviceName)
	},
})

ProvideNamed(container, "PG_URI", func(i *Container) (string, error) {
	return "postgres://user:pass@host:5432/db", nil
})
MustInvokeNamed[string](container, "PG_URI")
_ = container.Shutdown()
Output:

service shutdown: PG_URI

func (*Container) Clone ΒΆ

func (i *Container) Clone() *Container

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

Example ΒΆ
container := New()

ProvideNamedValue(container, "PG_URI", "postgres://user:pass@host:5432/db")
Container2 := container.Clone()
services := Container2.ListProvidedServices()

fmt.Println(services)
Output:

[PG_URI]

func (*Container) CloneWithOpts ΒΆ

func (i *Container) CloneWithOpts(opts *ContainerOpts) *Container

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

func (*Container) HealthCheck ΒΆ

func (i *Container) HealthCheck() map[string]error
Example ΒΆ
container := New()

Provide(container, dbServiceProvider)
health := container.HealthCheck()

fmt.Println(health)
Output:

map[*di.dbService:<nil>]

func (*Container) Inject ΒΆ

func (container *Container) Inject(servicePtr interface{}) error

func (*Container) ListInvokedServices ΒΆ

func (i *Container) ListInvokedServices() []string
Example (Invoked) ΒΆ
container := New()

type test struct {
	foobar string
}

ProvideNamed(container, "SERVICE_NAME", func(i *Container) (test, error) {
	return test{foobar: "foobar"}, nil
})
_, _ = InvokeNamed[test](container, "SERVICE_NAME")
services := container.ListInvokedServices()

fmt.Println(services)
Output:

[SERVICE_NAME]
Example (NotInvoked) ΒΆ
container := New()

type test struct {
	foobar string
}

ProvideNamed(container, "SERVICE_NAME", func(i *Container) (test, error) {
	return test{foobar: "foobar"}, nil
})
services := container.ListInvokedServices()

fmt.Println(services)
Output:

[]

func (*Container) ListProvidedServices ΒΆ

func (i *Container) ListProvidedServices() []string
Example ΒΆ
container := New()

ProvideNamedValue(container, "PG_URI", "postgres://user:pass@host:5432/db")
services := container.ListProvidedServices()

fmt.Println(services)
Output:

[PG_URI]

func (*Container) Shutdown ΒΆ

func (i *Container) Shutdown() error
Example ΒΆ
container := New()

Provide(container, dbServiceProvider)
err := container.Shutdown()

fmt.Println(err)
Output:

<nil>

func (*Container) ShutdownOnSIGTERM ΒΆ

func (i *Container) ShutdownOnSIGTERM() error

ShutdownOnSIGTERM listens for sigterm signal in order to graceful stop service. It will block until receiving a sigterm signal.

func (*Container) ShutdownOnSignals ΒΆ

func (i *Container) ShutdownOnSignals(signals ...os.Signal) error

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 will be added as default signal.

type ContainerOpts ΒΆ

type ContainerOpts struct {
	HookAfterRegistration func(injector *Container, serviceName string)
	HookAfterShutdown     func(injector *Container, serviceName string)

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

type Healthcheckable ΒΆ

type Healthcheckable interface {
	HealthCheck() error
}

type Provider ΒΆ

type Provider[T any] func(*Container) (T, error)

type Service ΒΆ

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

type Shutdownable ΒΆ

type Shutdownable interface {
	Shutdown() error
}

Directories ΒΆ

Path Synopsis
examples
healthcheckable command
interface command
shutdownable command
simple command

Jump to

Keyboard shortcuts

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