Dependency injection container allows you to inject dependencies
into constructors or structures without the need to have specified
each argument manually.
This container implementation inspired by google/wire,
uber-go/fx and uber-go/dig.
See godoc for feel the difference.
Contents
Installing
go get -u github.com/defval/inject
Type injection
Define constructors:
func NewHTTPServeMux() *http.ServeMux {
return &http.ServeMux{}
}
func NewHTTPServer(handler *net.ServeMux) (*http.Server, error) {
if os.Getenv("STATUS") == "stopped" {
return nil, errors.New("server stoped")
}
return &http.Server{
Handler: handler,
}, nil
}
Build container and extract values:
container, err := inject.New(
inject.Provide(NewHTTPServeMux),
inject.Provide(NewHTTPServer),
)
var server *http.Server
container.Extract(&server)
server.ListenAndServe()
Groups
When you have two or more implementations of same interface:
func NewUserController() *UserController {
return &UserController{}
}
func NewPostController() *PostController {
return &PostController()
}
type Controller interface {
RegisterRoutes()
}
Group it!
var IController = new(Controller)
container, err := inject.New(
inject.Provide(NewUserController, inject.As(IController)),
inject.Provide(NewPostController, inject.As(IController)),
)
var controllers []Controller
container.Extract(&controllers)
for _, ctrl := range controllers {
ctrl.RegisterRoutes()
}
Return structs, accept interfaces!
Bind implementations as interfaces:
func NewServeMux() *http.ServeMux {
return &http.ServeMux{}
}
func NewServer(handler http.Handler) *http.Server {
return &http.Server{
Handler: handler,
}
}
Provide concrete implementation as interface:
var IHandler = new(http.Handler)
container, err := inject.New(
inject.Provide(NewServeMux, inject.As(IHandler)),
inject.Provide(NewServer),
)
var handler http.Handler
container.Extract(&handler)
var server *http.Server
container.Extract(&server)
Bundles
var ProcessingBundle = inject.Bundle(
inject.Provide(processing.NewDispatcher),
inject.Provide(processing.NewProvider),
inject.Provide(processing.NewProxy, inject.As(IProxy)),
)
var BillingBundle = inject.Bundle(
inject.Provide(billing.NewInteractor),
inject.Provide(billing.NewInvoiceRepository, inject.As(new(InvoiceRepository)))
)
And test each one separately.
func TestProcessingBundle(t *testing.T) {
bundle, err := inject.New(
ProcessingBundle,
inject.Replace(processing.NewDevProxy, inject.As(IProxy)),
)
var dispatcher *processing.Dispatcher
container.Extract(&dispatcher)
dispatcher.Dispatch(ctx context.Context, thing)
}
Replace
var options []inject.Options
if os.Getenv("ENV") == "dev" {
options = append(options, inject.Replace(billing.NewInvoiceRepositoryMock), inject.As(new(InvoiceRepository)))
}
container, err := inject.New(options...)
Named definitions
container, err := inject.New{
inject.Provide(NewDefaultServer, inject.WithName("default")),
inject.Provide(NewAdminServer, inject.WithName("admin")),
}
var defaultServer *http.Server
var adminServer *http.Server
container.Extract(&defaultServer, inject.Name("default"))
container.Extract(&adminServer, inject.Name("admin"))
Or with struct provider:
type Application struct {
Server *http.Server `inject:"default"`
AdminServer *http.Server `inject:"admin"`
}
container, err := inject.New(
inject.Provide(NewDefaultServer, inject.WithName("default")),
inject.Provide(NewAdminServer, inject.WithName("admin")),
inject.Provide(&Application)
)
If you don't like tags as much as I do, then look to
inject.Exported()
provide option.
Use combined provider
For advanced providing use combined provider. It's both - struct and constructor providers.
type ServerProvider struct {
Mux *http.Server `inject:"dude_mux"`
}
func (p *ServerProvider) Provide() *http.Server {
return &http.Server{
Handler: p.Mux,
}
}
Write visualization into io.Writer
. Check out result on graphviz online tool!
buffer := &bytes.Buffer{}
container.WriteTo(buffer)
or
var graph *dot.Graph
container.Extract(&graph)
graph.Write(buffer)
This is visualization of container example.