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.
go get -u github.com/defval/inject
Define constructors:
// NewHTTPHandler is a http mux constructor.
func NewHTTPServeMux() *http.ServeMux {
return &http.ServeMux{}
}
// NewHTTPServer is a http server constructor, handler will be injected
// by container. If environment variable `STATUS == "stoped"` extract
// server cause error.
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:
// build container
container, err := inject.New(
inject.Provide(NewHTTPServeMux), // provide mux
inject.Provide(NewHTTPServer), // provide server
)
// don't forget to handle errors © golang
// define variable for *http.Server
var server *http.Server
// extract into this variable
container.Extract(&server)
// use it!
server.ListenAndServe()
When you have two or more implementations of same interface:
// NewUserController
func NewUserController() *UserController {
return &UserController{}
}
// NewPostController
func NewPostController() *PostController {
return &PostController()
}
// Controller
type Controller interface {
RegisterRoutes()
}
Group it!
// IController is a java style interface alias =D
// inject.As(new(Controller)) looks worse in readme.
var IController = new(Controller)
container, err := inject.New(
inject.Provide(NewUserController, inject.As(IController)),
inject.Provide(NewPostController, inject.As(IController)),
)
var controllers []Controller
// extract all controllers
container.Extract(&controllers)
// and do something!!!
for _, ctrl := range controllers {
ctrl.RegisterRoutes()
}
Bind implementations as interfaces:
// NewHandler is a http mux constructor. Returns concrete
// implementation - *http.ServeMux.
func NewServeMux() *http.ServeMux {
return &http.ServeMux{}
}
// NewServer is a http server constructor. Needs handler for
// working.
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) // *http.ServeMux will be extracted
var server *http.Server
container.Extract(&server) // server.Handler is *http.ServeMux
// ProcessingBundle responsible for processing
var ProcessingBundle = inject.Bundle(
inject.Provide(processing.NewDispatcher),
inject.Provide(processing.NewProvider),
inject.Provide(processing.NewProxy, inject.As(IProxy)),
)
// BillingBundle responsible for billing
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)
}
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...)
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:
// Application
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.
For advanced providing use combined provider. It's both - struct and constructor providers.
// ServerProvider
type ServerProvider struct {
Mux *http.Server `inject:"dude_mux"`
}
// Provide is a container predefined constructor function for *http.Server.
func (p *ServerProvider) Provide() *http.Server {
return &http.Server{
Handler: p.Mux,
}
}
Visualize (Graphviz)
Write visualization into io.Writer
. Check out result on graphviz online tool!
// visualization data target
buffer := &bytes.Buffer{}
// write container visualization
container.WriteTo(buffer)
This is visualization of container example.