do

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: May 18, 2022 License: MIT Imports: 3 Imported by: 366

README

do

tag codecov Build Status GoDoc Go report

✨ 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 in simple Go projects. samber/do uses Go 1.18+ generics instead of reflection and therefore is typesafe.

See also:

  • samber/lo: A Lodash-style Go library based on Go 1.18+ Generics
  • samber/mo: Monads based on Go 1.18+ Generics
Why this name?

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

💡 Features

  • Service registration
  • Service invocation
  • Service health check
  • Service shutdown
  • Named or anonymous services
  • Eagerly or lazily loaded services
  • Dependency graph resolution
  • Default injector

🚀 Services are loaded in invocation order.

🕵️ Service health can be checked individually or globally. Services implementing do.Healthcheckable interface will be called via do.HealthCheck[type]() or injector.HealthCheck().

🛑 Services can be shutdowned properly, in back-initialization order. Services implementing do.Shutdownable interface will be called via do.Shutdown[type]() or injector.Shutdown().

🚀 Install

go get github.com/samber/do@v1

This library is v1 and follows SemVer strictly.

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

💡 Quick start

You can import do using:

import (
    "github.com/samber/do"
)

Then instanciate services:

type EngineService interface{}

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

type engineServiceImplem struct {}

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

type CarService struct {
	Engine EngineService
}

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

// [Optional] Implements do.Shutdownable.
func (c *CarService) Shutdown() error {
	println("car stopped")
	return nil
}
func main() {
    injector := do.New()

	// provides CarService
    do.Provide(injector, NewCarService)

	// provides EngineService 
	do.Provide(injector, NewEngineService)

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

	do.HealthCheck[EngineService](injector)
    // returns "engine broken"

	injector.Shutdown()
    // prints "car stopped"
}

🤠 Spec

GoDoc: https://godoc.org/github.com/samber/do

Injector (DI container)

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

injector := do.New()

Or use nil as the default injector:

do.Provide(nil, func (i *Injector) (int, error) {
    return 42, nil
})

service := do.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()
}

injector := do.New()
do.Provide(injector, ...)
do.Invoke(injector, ...)

statuses := injector.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()
}

injector := do.New()
do.Provide(injector, ...)
do.Invoke(injector, ...)

// shutdown all services in reverse order
injector.Shutdown()
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
}

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

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

Anonymous service, loaded lazily:

type DBService struct {
    db *sql.DB
}

do.ProvideNamed(injector, "dbconn", func(i *Injector) (*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
}

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

Named service, loaded eagerly:

type Config struct {
    uri string
}

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

Loads anonymous service:

type DBService struct {
    db *sql.DB
}

dbService, err := do.Invoke[DBService](injector)

Loads anonymous service or panics if service was not registered:

type DBService struct {
    db *sql.DB
}

dbService := do.MustInvoke[DBService](injector)

Loads named service:

config, err := do.InvokeNamed[Config](injector, "configuration")

Loads named service or panics if service was not registered:

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

Check health of anonymous service:

type DBService struct {
    db *sql.DB
}

dbService, err := do.Invoke[DBService](injector)
err = do.HealthCheck[DBService](injector)

Check health of named service:

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

Unloads anonymous service:

type DBService struct {
    db *sql.DB
}

dbService, err := do.Invoke[DBService](injector)
err = do.Shutdown[DBService](injector)

Unloads anonymous service or panics if service was not registered:

type DBService struct {
    db *sql.DB
}

dbService := do.MustInvoke[DBService](injector)
do.MustShutdown[DBService](injector)

Unloads named service:

config, err := do.InvokeNamed[Config](injector, "configuration")
err = do.ShutdownNamed(injector, "configuration")

Unloads named service or panics if service was not registered:

config := do.MustInvokeNamed[Config](injector, "configuration")
do.MustShutdownNamed(injector, "configuration")

🛩 Benchmark

// @TODO

This library does not use reflect package. We don't expect overhead.

🤝 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

👤 Authors

  • Samuel Berthe

💫 Show your support

Give a ⭐️ if this project helped you!

support us

📝 License

Copyright © 2022 Samuel Berthe.

This project is MIT licensed.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultInjector = New()

Functions

func HealthCheck

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

func HealthCheckNamed

func HealthCheckNamed(i *Injector, name string) error

func Invoke

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

func InvokeNamed

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

func MustInvoke

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

func MustInvokeNamed

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

func MustShutdown

func MustShutdown[T any](i *Injector)

func MustShutdownNamed

func MustShutdownNamed(i *Injector, name string)

func Provide

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

func ProvideNamed

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

func ProvideNamedValue

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

func ProvideValue

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

func Shutdown

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

func ShutdownNamed

func ShutdownNamed(i *Injector, name string) error

Types

type Healthcheckable

type Healthcheckable interface {
	HealthCheck() error
}

type Injector

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

func New

func New() *Injector

func (*Injector) HealthCheck

func (i *Injector) HealthCheck() map[string]error

func (*Injector) Shutdown

func (i *Injector) Shutdown() error

type Provider

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

type Service

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

type ServiceEager

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

type ServiceLazy

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

type Shutdownable

type Shutdownable interface {
	Shutdown() error
}

Directories

Path Synopsis
examples
http/gin module
http
chi module
echo module
fiber module
gin module
std module

Jump to

Keyboard shortcuts

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