- It is simpler than uber/fx, yet still powerful
- Static typing & without using reflection
- Easy to use and easy to replace objects for unit & integration testing (by using Override, one can replace any part of the default dependency graph)
- Easy to read & navigate with IDEs
- Safe to use in multiple goroutines
- Very easy-to-read stacktraces when something wrong happens
- Provide default wiring through the Register method, or no default wiring through RegisterEmpty
- Runtime wiring of objects
- Deep stack calls
Override
functions only work outside of theanonymous functions
in theRegister
calls
go get github.com/QuangTung97/[email protected]
Assume RepoImpl
implements interface Repo
:
package main
type Repo interface {
GetUser() string
}
type RepoImpl struct {
name string
}
func NewRepo(name string) *RepoImpl {
return &RepoImpl{
name: name,
}
}
func (r *RepoImpl) GetUser() string {
return r.name
}
Assume Service
with method Hello
:
package main
import (
"fmt"
)
type Service struct {
rp Repo
}
func NewService(repo Repo) *Service {
return &Service{
rp: repo,
}
}
func (s *Service) Hello() {
fmt.Println("Hello User:", s.rp.GetUser())
}
We can create Locator[T]
objects:
package main
import (
"github.com/QuangTung97/svloc"
)
var usernameLoc = svloc.RegisterEmpty[string]()
var repoLoc = svloc.Register[Repo](func(unv *svloc.Universe) Repo {
return NewRepo(
usernameLoc.Get(unv),
)
})
var serviceLoc = svloc.Register[*Service](func(unv *svloc.Universe) *Service {
return NewService(repoLoc.Get(unv))
})
The 3 newly created objects: usernameLoc
, repoLoc
, serviceLoc
are all immutable objects and safe to use concurrently.
The svloc.PreventRegistering
will not allow Register
functions to be called after that point.
Usually at the start of the main()
function.
To use in main()
, first creates a new Universe
.
Then call MustOverride()
on usernameLoc
to provide the username string.
And then call the serviceLoc.Get()
with that Universe
,
All of the wiring will happen automatically:
package main
func main() {
svloc.PreventRegistering()
unv := svloc.NewUniverse()
usernameLoc.MustOverride(unv, "user01")
svc := serviceLoc.Get(unv)
svc.Hello()
}
Full example:
package main
import (
"fmt"
"github.com/QuangTung97/svloc"
)
type Repo interface {
GetUser() string
}
type RepoImpl struct {
name string
}
func NewRepo(name string) *RepoImpl {
return &RepoImpl{
name: name,
}
}
func (r *RepoImpl) GetUser() string {
return r.name
}
type Service struct {
rp Repo
}
func NewService(repo Repo) *Service {
return &Service{
rp: repo,
}
}
func (s *Service) Hello() {
fmt.Println("Hello User:", s.rp.GetUser())
}
var usernameLoc = svloc.RegisterEmpty[string]()
var repoLoc = svloc.Register[Repo](func(unv *svloc.Universe) Repo {
return NewRepo(
usernameLoc.Get(unv),
)
})
var serviceLoc = svloc.Register[*Service](func(unv *svloc.Universe) *Service {
return NewService(repoLoc.Get(unv))
})
func main() {
svloc.PreventRegistering()
unv := svloc.NewUniverse()
usernameLoc.MustOverride(unv, "user01")
svc := serviceLoc.Get(unv)
svc.Hello()
}
Using OnShutdown
and Shutdown
:
package main
var repoLoc = svloc.Register[Repo](func(unv *svloc.Universe) Repo {
unv.OnShutdown(func() {
fmt.Println("Shutdown Repo")
})
return NewRepo(
usernameLoc.Get(unv),
)
})
var serviceLoc = svloc.Register[*Service](func(unv *svloc.Universe) *Service {
unv.OnShutdown(func() {
fmt.Println("Shutdown Service")
})
return NewService(repoLoc.Get(unv))
})
func main() {
svloc.PreventRegistering()
unv := svloc.NewUniverse()
defer unv.Shutdown()
usernameLoc.MustOverride(unv, "user01")
svc := serviceLoc.Get(unv)
svc.Hello()
}