This guide gets you from zero to a resolving container. For concepts and vocabulary, see Overview.
- Go 1.23 or newer
go get github.com/MY-RV/goxdiA Builder holds registrations until you call Build. Each registration pairs a type T with a factory that returns (T, error).
package main
import (
"fmt"
"github.com/MY-RV/goxdi"
)
type Greeter struct {
Msg string
}
func main() {
b := goxdi.NewBuilder()
if err := goxdi.AddSingleton(b, func(goxdi.Resolver) (*Greeter, error) {
return &Greeter{Msg: "hello from goxdi"}, nil
}); err != nil {
panic(err)
}
root := b.Build()
defer root.Close()
g, err := goxdi.Get[*Greeter](root)
if err != nil {
panic(err)
}
fmt.Println(g.Msg)
}What this does:
NewBuildercreates an empty registry.AddSingletonprovides*Greeterfor the lifetime of the container.Buildfreezes registrations into a rootContainer.Get[*Greeter]resolves the instance (creating it on first use).Closedisposes trackedio.Closersingletons (none here).
Factories receive a Resolver. Use it to ask for other services instead of constructing them inline.
type DB struct{ Name string }
type Repo struct{ DB *DB }
b := goxdi.NewBuilder()
_ = goxdi.AddSingleton(b, func(goxdi.Resolver) (*DB, error) {
return &DB{Name: "main"}, nil
})
_ = goxdi.AddScoped(b, func(r goxdi.Resolver) (*Repo, error) {
db, err := goxdi.Get[*DB](r)
if err != nil {
return nil, err
}
return &Repo{DB: db}, nil
})
root := b.Build()
defer root.Close()
scope := root.NewScope()
defer scope.Close()
repo := goxdi.MustGet[*Repo](scope)
fmt.Println(repo.DB.Name)IMPORTANT: *Repo is scoped. Resolving it from root returns ErrScopeRequired. Open a scope first.
Use this when wiring a real process:
- Put registrations in one composition root (
mainor a dedicatedwirepackage). - Register every type you will resolve, including interface tokens consumers depend on.
- Call
Build()once; keep the root for the process lifetime. - Call
NewScope()once per unit of work (request, job, CLI command). - Prefer
Getwhere errors should propagate; useMustGetinside factories you fully control. - Always
defer scope.Close()anddefer root.Close().
You now have the happy path. Specialized guides cover the hard edges:
- Registering services — duplicates, interfaces, factory shape
- Lifetimes — singleton vs scoped vs transient
- Scopes — nesting and disposal
- Resolving —
GetvsMustGetand circular graphs