Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/samber/do
⚙️ A dependency injection toolkit based on Go 1.18+ Generics.
https://github.com/samber/do
container dependency dependency-graph di generics go golang graceful-shutdown healthcheck injection injector invoke ioc lifecycle provide service typesafe
Last synced: 21 days ago
JSON representation
⚙️ A dependency injection toolkit based on Go 1.18+ Generics.
- Host: GitHub
- URL: https://github.com/samber/do
- Owner: samber
- License: mit
- Created: 2022-05-17T22:10:38.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2024-08-15T09:45:08.000Z (3 months ago)
- Last Synced: 2024-10-01T22:43:03.312Z (about 1 month ago)
- Topics: container, dependency, dependency-graph, di, generics, go, golang, graceful-shutdown, healthcheck, injection, injector, invoke, ioc, lifecycle, provide, service, typesafe
- Language: Go
- Homepage: https://pkg.go.dev/github.com/samber/do
- Size: 1.81 MB
- Stars: 1,818
- Watchers: 24
- Forks: 75
- Open Issues: 31
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome-go - do - A dependency injection framework based on Generics. (Miscellaneous / Dependency Injection)
- my-awesome - samber/do - graph,di,generics,go,golang,graceful-shutdown,healthcheck,injection,injector,invoke,ioc,lifecycle,provide,service,typesafe pushed_at:2024-08 star:1.9k fork:0.1k ⚙️ A dependency injection toolkit based on Go 1.18+ Generics. (Go)
- zero-alloc-awesome-go - do - A dependency injection framework based on Generics. (Miscellaneous / Dependency Injection)
- awesome-ccamel - samber/do - ⚙️ A dependency injection toolkit based on Go 1.18+ Generics. (Go)
- awesome-go - do - A dependency injection framework based on Generics. Stars:`1.9K`. (Miscellaneous / Dependency Injection)
- awesome-go-extra - do - 05-17T22:10:38Z|2022-08-06T23:51:11Z| (Microsoft Office / Dependency Injection)
README
# do - Dependency Injection
[![tag](https://img.shields.io/github/tag/samber/do.svg)](https://github.com/samber/do/releases)
![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.18-%23007d9c)
[![GoDoc](https://godoc.org/github.com/samber/do?status.svg)](https://pkg.go.dev/github.com/samber/do)
![Build Status](https://github.com/samber/do/actions/workflows/test.yml/badge.svg)
[![Go report](https://goreportcard.com/badge/github.com/samber/do)](https://goreportcard.com/report/github.com/samber/do)
[![Coverage](https://img.shields.io/codecov/c/github/samber/do)](https://codecov.io/gh/samber/do)
[![License](https://img.shields.io/github/license/samber/do)](./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 in simple Go projects. `samber/do` uses Go 1.18+ generics and therefore offers a typesafe API.
**See also:**
- [samber/lo](https://github.com/samber/lo): A Lodash-style Go library based on Go 1.18+ Generics
- [samber/mo](https://github.com/samber/mo): Monads based on Go 1.18+ Generics (Option, Result, Either...)**Why this name?**
I love **short name** for such a utility library. This name is the sum of `DI` and `Go` and no Go package currently uses this name.
**⭕⭕⭕⭕⭕⭕ About v2 ⭕⭕⭕⭕⭕⭕**
Check out the beta now!
```bash
go get -u github.com/samber/do/[email protected]
```Documentation: https://do.samber.dev/
Please report bugs here: [#45](https://github.com/samber/do/pull/45).
## 💡 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 injector
- Injector 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 `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
```sh
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.
This library has no dependencies except the Go std lib.
## 💡 Quick start
You can import `do` using:
```go
import (
"github.com/samber/do"
)
```Then instantiate services:
```go
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.ShutdownOnSIGTERM() // will block until receiving sigterm signal
injector.Shutdown()
// prints "car stopped"
}
```Services:
```go
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")
}
``````go
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
}
```## 🤠 Spec
[GoDoc: https://godoc.org/github.com/samber/do](https://godoc.org/github.com/samber/do)
Injector:
- [do.New](https://pkg.go.dev/github.com/samber/do#New)
- [do.NewWithOpts](https://pkg.go.dev/github.com/samber/do#NewWithOpts)
- [injector.Clone](https://pkg.go.dev/github.com/samber/do#injector.Clone)
- [injector.CloneWithOpts](https://pkg.go.dev/github.com/samber/do#injector.CloneWithOpts)
- [injector.HealthCheck](https://pkg.go.dev/github.com/samber/do#injector.HealthCheck)
- [injector.Shutdown](https://pkg.go.dev/github.com/samber/do#injector.Shutdown)
- [injector.ShutdownOnSIGTERM](https://pkg.go.dev/github.com/samber/do#injector.ShutdownOnSIGTERM)
- [injector.ShutdownOnSignals](https://pkg.go.dev/github.com/samber/do#injector.ShutdownOnSignals)
- [injector.ListProvidedServices](https://pkg.go.dev/github.com/samber/do#injector.ListProvidedServices)
- [injector.ListInvokedServices](https://pkg.go.dev/github.com/samber/do#injector.ListInvokedServices)
- [do.HealthCheck](https://pkg.go.dev/github.com/samber/do#HealthCheck)
- [do.HealthCheckNamed](https://pkg.go.dev/github.com/samber/do#HealthCheckNamed)
- [do.Shutdown](https://pkg.go.dev/github.com/samber/do#Shutdown)
- [do.ShutdownNamed](https://pkg.go.dev/github.com/samber/do#ShutdownNamed)
- [do.MustShutdown](https://pkg.go.dev/github.com/samber/do#MustShutdown)
- [do.MustShutdownNamed](https://pkg.go.dev/github.com/samber/do#MustShutdownNamed)Service registration:
- [do.Provide](https://pkg.go.dev/github.com/samber/do#Provide)
- [do.ProvideNamed](https://pkg.go.dev/github.com/samber/do#ProvideNamed)
- [do.ProvideNamedValue](https://pkg.go.dev/github.com/samber/do#ProvideNamedValue)
- [do.ProvideValue](https://pkg.go.dev/github.com/samber/do#ProvideValue)Service invocation:
- [do.Invoke](https://pkg.go.dev/github.com/samber/do#Invoke)
- [do.MustInvoke](https://pkg.go.dev/github.com/samber/do#MustInvoke)
- [do.InvokeNamed](https://pkg.go.dev/github.com/samber/do#InvokeNamed)
- [do.MustInvokeNamed](https://pkg.go.dev/github.com/samber/do#MustInvokeNamed)Service override:
- [do.Override](https://pkg.go.dev/github.com/samber/do#Override)
- [do.OverrideNamed](https://pkg.go.dev/github.com/samber/do#OverrideNamed)
- [do.OverrideNamedValue](https://pkg.go.dev/github.com/samber/do#OverrideNamedValue)
- [do.OverrideValue](https://pkg.go.dev/github.com/samber/do#OverrideValue)### Injector (DI container)
Build a container for your components. `Injector` is responsible for building services in the right order, and managing service lifecycle.
```go
injector := do.New()
```Or use `nil` as the default injector:
```go
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`.
```go
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.
```go
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()
```List services:
```go
type DBService struct {
db *sql.DB
}injector := do.New()
do.Provide(injector, ...)
println(do.ListProvidedServices())
// output: []string{"*DBService"}do.Invoke(injector, ...)
println(do.ListInvokedServices())
// output: []string{"*DBService"}
```### Service registration
Services can be registered in multiple way:
- with implicit name (struct or interface name)
- with explicit name
- eagerly
- lazilyAnonymous service, loaded lazily:
```go
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
})
```Named service, loaded lazily:
```go
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:
```go
type Config struct {
uri string
}do.ProvideValue[Config](injector, Config{uri: "postgres://user:pass@host:5432/db"})
```Named service, loaded eagerly:
```go
type Config struct {
uri string
}do.ProvideNamedValue(injector, "configuration", Config{uri: "postgres://user:pass@host:5432/db"})
```### Service invocation
Loads anonymous service:
```go
type DBService struct {
db *sql.DB
}dbService, err := do.Invoke[DBService](injector)
```Loads anonymous service or panics if service was not registered:
```go
type DBService struct {
db *sql.DB
}dbService := do.MustInvoke[DBService](injector)
```Loads named service:
```go
config, err := do.InvokeNamed[Config](injector, "configuration")
```Loads named service or panics if service was not registered:
```go
config := do.MustInvokeNamed[Config](injector, "configuration")
```### Individual service healthcheck
Check health of anonymous service:
```go
type DBService struct {
db *sql.DB
}dbService, err := do.Invoke[DBService](injector)
err = do.HealthCheck[DBService](injector)
```Check health of named service:
```go
config, err := do.InvokeNamed[Config](injector, "configuration")
err = do.HealthCheckNamed(injector, "configuration")
```### Individual service shutdown
Unloads anonymous service:
```go
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:
```go
type DBService struct {
db *sql.DB
}dbService := do.MustInvoke[DBService](injector)
do.MustShutdown[DBService](injector)
```Unloads named service:
```go
config, err := do.InvokeNamed[Config](injector, "configuration")
err = do.ShutdownNamed(injector, "configuration")
```Unloads named service or panics if service was not registered:
```go
config := do.MustInvokeNamed[Config](injector, "configuration")
do.MustShutdownNamed(injector, "configuration")
```### Service override
By default, providing a service twice will panic. Service can be replaced at runtime using `do.Override` helper.
```go
do.Provide[Vehicle](injector, func (i *do.Injector) (Vehicle, error) {
return &CarImplem{}, nil
})do.Override[Vehicle](injector, func (i *do.Injector) (Vehicle, error) {
return &BusImplem{}, nil
})
```### Hooks
2 lifecycle hooks are available in Injectors:
- After registration
- After shutdown```go
injector := do.NewWithOpts(&do.InjectorOpts{
HookAfterRegistration: func(injector *do.Injector, serviceName string) {
fmt.Printf("Service registered: %s\n", serviceName)
},
HookAfterShutdown: func(injector *do.Injector, serviceName string) {
fmt.Printf("Service stopped: %s\n", serviceName)
},Logf: func(format string, args ...any) {
log.Printf(format, args...)
},
})
```### Cloning injector
Cloned injector 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.
```go
var injector *do.Injector;func init() {
do.Provide[Service](injector, func (i *do.Injector) (Service, error) {
return &RealService{}, nil
})
do.Provide[*App](injector, func (i *do.Injector) (*App, error) {
return &App{i.MustInvoke[Service](i)}, nil
})
}func TestService(t *testing.T) {
i := injector.Clone()
defer i.Shutdown()// replace Service to MockService
do.Override[Service](i, func (i *do.Injector) (Service, error) {
return &MockService{}, nil
}))app := do.Invoke[*App](i)
// do unit testing with mocked service
}
```## 🛩 Benchmark
// @TODO
## 🤝 Contributing
- Ping me on twitter [@samuelberthe](https://twitter.com/samuelberthe) (DMs, mentions, whatever :))
- Fork the [project](https://github.com/samber/do)
- Fix [open issues](https://github.com/samber/do/issues) or request new featuresDon't hesitate ;)
### With Docker
```bash
docker-compose run --rm dev
```### Without Docker
```bash
# Install some dev dependencies
make tools# Run tests
make test
# or
make watch-test
```## 👤 Contributors
![Contributors](https://contrib.rocks/image?repo=samber/do)
## 💫 Show your support
Give a ⭐️ if this project helped you!
[![GitHub Sponsors](https://img.shields.io/github/sponsors/samber?style=for-the-badge)](https://github.com/sponsors/samber)
## 📝 License
Copyright © 2022 [Samuel Berthe](https://github.com/samber).
This project is [MIT](./LICENSE) licensed.