🚧 This library as under heavy development until release of version 1.x.x
🚧
Dependency injection for Go projects, targeted for web applications that create DI context during the startup and operates on collections of dependencies.
- Register dependencies by name and type
- Register multiple dependencies per type
- Conditional dependency registration
- Simple dependency retrieval - no manual casting or additional callbacks
- Simple setup - no generators
- Detection of slow dependency creation (TODO)
- Initialization and finalization mechanisms (TODO)
Get the dependency with:
go get github.com/coditory/go-di
and import it in the project:
import "github.com/coditory/go-di"
The exported package is di
, basic usage:
import "github.com/coditory/go-di"
func main() {
ctxb := di.NewContextBuilder()
// Add dependencies
ctxb.Add(&foo)
// Build the di context
ctx := ctxb.Build()
// Retrieve dependencies from the context
}
package main
import (
"fmt"
"github.com/coditory/go-di"
)
type Baz interface { Id() string }
type Foo struct { id string }
func (f *Foo) Id() string { return f.id }
type Bar struct { id string }
func (b *Bar) Id() string { return b.id }
func main() {
foo := Foo{id: "foo1"}
foo2 := Foo{id: "foo2"}
bar := Bar{id: "baz"}
ctxb := di.NewContextBuilder()
ctxb.AddAs(new(Baz), &foo)
ctxb.AddAs(new(Baz), &foo2)
ctxb.AddAs(new(Baz), &bar)
ctx := ctxb.Build()
fmt.Printf("first implementation: %+v\n", di.Get[Baz](ctx))
for i, baz := range di.GetAll[Baz](ctx) {
fmt.Printf("%d: %+v\n", i, baz)
}
}
// output:
// first implementation: &{id:foo1}
// 0: &{id:foo1}
// 1: &{id:foo2}
// 2: &{id:baz}
Add dependency reference and retrieve by the reference type:
ctxb := di.NewContextBuilder()
ctxb.Add(&foo)
ctxb.Add(&foo2)
ctx := ctxb.Build()
suite.Equal(&foo, di.Get[*Foo](ctx))
suite.Equal([]*Foo{&foo, &foo2}, di.GetAll[*Foo](ctx))
Add dependency (by value) and retrieve by the type:
ctxb := di.NewContextBuilder()
ctxb.Add(foo)
ctxb.Add(foo2)
ctx := ctxb.Build()
suite.Equal(foo, di.Get[Foo](ctx))
foos := di.GetAll[Foo](ctx)
suite.Equal(2, len(foos))
suite.Equal("foo", foos[0].Id())
suite.Equal("foo2", foos[1].Id())
To retrieve a dependency by the interface it must be registered explicitly by the interface type:
ctxb := di.NewContextBuilder()
// ctx.Add(&foo) <- will not work!
ctxb.AddAs(new(Baz), &foo)
ctxb.AddAs(new(Baz), &foo2)
ctx := ctxb.Build()
suite.Equal(&foo, di.Get[Baz](ctx))
suite.Equal([]Baz{&foo, &foo2}, di.GetAll[Baz](ctx))
Single dependency can be registered multiple times:
ctxb := di.NewContextBuilder()
ctxb.Add(&foo)
ctxb.AddAs(new(Baz), &foo)
ctxb.Add(&foo2)
ctxb.AddAs(new(Baz), &foo2)
ctx := ctxb.Build()
suite.Equal(&foo, di.Get[Baz](ctx))
suite.Equal([]Baz{&foo, &foo2}, di.GetAll[Baz](ctx))
suite.Equal(&foo, di.Get[*Foo](ctx))
suite.Equal([]*Foo{&foo, &foo2}, di.GetAll[*Foo](ctx))
To differentiate dependencies of the same type you can name them. There can be only one dependency per name.
ctxb := di.NewContextBuilder()
ctxb.AddAs(new(Baz), &foo)
ctxb.AddNamedAs("special-foo", new(Baz), &foo2)
// ctxb.AddNamedAs("special-foo", new(Baz), &foo3) <- panics
ctx := ctxb.Build()
suite.Equal(&foo, di.Get[Baz](ctx))
suite.Equal(&foo2, di.GetNamed[Baz](ctx, "special-foo"))
suite.Equal([]Baz{&foo, &foo2}, di.GetAll[Baz](ctx))
Lazy dependencies are created when retrieved:
type Boo struct {
foo *Foo
bar *Bar
}
ctxb := di.NewContextBuilder()
ctxb.Provide(func() *Foo {
return &foo
})
ctxb.Provide(func() *Bar {
return &bar
})
ctxb.Provide(func(foo *Foo, bar *Bar) *Boo {
return &Boo{foo: foo, bar: bar}
})
ctx := ctxb.Build()
boo := di.Get[*Boo](ctx)
suite.Equal(&foo, boo.foo)
suite.Equal(&bar, boo.bar)
If a dependency requires a long list of other dependencies then inject *Context
:
ctxb.Provide(func(ctx *di.Context) *Boo {
return &Boo{foo: di.Get[*Foo](ctx), bar: di.Get[*Bar](ctx)}
})
There are the same variations for ctxb.Add*
and ctxb.Provide*
functions:
ctxb.Provide(createFoo)
ctxb.ProvideAs(new(Baz), createFoo)
ctxb.ProvideNamed("special-foo", createFoo)
ctxb.ProvideNamedAs("special-foo", new(Baz), createFoo)
When dependency creator is a separate (non-inlined) function, then creation happens only once:
creations := 0
createFoo := func() *Foo {
creations++
return &foo
}
ctxb := di.NewContextBuilder()
ctxb.Provide(createFoo)
ctxb.ProvideAs(new(Baz), createFoo)
ctx := ctxb.Build()
di.Get[Baz](ctx)
di.Get[*Foo](ctx)
suite.Equal(1, creations)