Minidic is a small Dependency Injection Container for the Go language, ported from PHP's Pimple, that consists of just one file and two public interfaces (about 200 lines of code).
The test suite, and even this README file, are basically a copy-n-paste of Pimple's ones, with only a light adaptation to Go and the addition of a way define services to with pre-resolved dependencies.
So all kudos go to Fabien Potencier and to Pimple contributors!
Install it:
$ go get -u github.com/DrBenton/minidic
Then import it in your code, and you're good to go:
import dic "github.com/DrBenton/minidic"
Creating a container is a matter of instating the a Container
interface:
container := dic.NewContainer()
As many other dependency injection containers, Minidic is able to manage two different kind of data: services and parameters.
(note that a quick look at the test suite can also give you a pretty good overview of this module features)
Defining a parameter is as simple as using the Container Add(newInjection Injection)
method:
// define some parameters
container.Add(dic.NewInjection("cookie_name", "SESSION_ID"))
container.Add(dic.NewInjection("cookie_ttl", 3600))
A service is an object that does something as part of a larger system. Examples of services: Database connection, templating engine, mailer. Almost any object could be a service.
Services are defined by functions that return an instance of an object:
// define some services
func getSessionStorageConfig(c dic.Container) SessionStorageConfig {
cookieName := c.Get("cookie_name").(string)
cookieTTL := c.Get("cookie_ttl").(int)
return SessionStorageConfig{cookieName, cookieTTL}
}
container.Add(dic.NewInjection("session_storage_config", getSessionStorageConfig))
container.Add(dic.NewInjection("session_storage", func getSessionStorageConfig(c dic.Container) SessionStorage {
return NewSessionStorage(c.Get("session_storage_config").(SessionStorageConfig))
})
Notice that the function has access to the current container instance, allowing references to other services or parameters.
As objects are only created when you get them, the order of the definitions does not matter, and there is no noticeable performance penalty.
Using the defined services is also very easy:
// get the session storage object
session := container.Get("session_storage").(SessionStorage)
// the above call is roughly equivalent to the following code:
// sessionStorage := SessionStorageConfig{"SESSION_ID", 3600}
// session := NewSessionStorage(sessionStorage)
You can also set a slice of services ids, with the WithInjectedDependencies([]string)
method
of the Injection interface. That way, you don't have to inject the container and
manually retrieve your service dependencies: they are already resolved when your service function
is called!
container.Add(dic.NewInjection(
"mailer.transport",
func(smtpServer string, port uint) MailerTransport {
return &MailerSmtpTransport{smtpServer, port}
},
).WithInjectedDependencies([]string{"mailer.transport.smtp", "mailer.transport.port"}))
container.Add(dic.NewInjection(
"mailer",
func(transport *MailerTransport, defaultSender string, debugMode bool) Mailer {
return Mailer{transport, defaultSender, debugMode}
},
).WithInjectedDependencies([]string{"mailer.transport", "mailer.default_sender", "app.debug"}))
container.Add(dic.NewInjection("mailer.transport.smtp", "smtp.google.com"))
container.Add(dic.NewInjection("mailer.transport.port", 10052))
container.Add(dic.NewInjection("mailer.default_sender", "[email protected]"))
// So any service can now have a shared instance of the Mailer:
mailer := container.Get("mailer").(Mailer)
By default, each time you get a service, Minidic returns the same instance of it. If you want a different instance to be returned for all calls, mark the service as being a "factory":
container.Add(dic.NewInjection("incident_context", generateNewIncidentContext).MarkAsFactory())
Now, each call to container.Get("incident_context")
returns a new instance of the service.
Because Minidic sees functions as service definitions, you need to mark the service as being a "protected" one to store them as parameter:
container.Add(dic.NewInjection("random_generator", myRandonGeneratorFunction).MarkAsProtected())
In some cases you may want to modify a service definition after it has been
defined. You can use the extend()
method to define additional code to
be run on your service just after it is created:
container.Add(dic.NewInjection("mailer", func (c dic.Container) Mailer {
return NewMailJetMailer(c.Get("mailjet.login").(string), c.Get("mailjet.password").(string))
})
if debug {
container.Extend("mailer", func(c dic.Container, decoratedMailer Mailer) Mailer {
return DebugMailer{decoratedMailer, c.Get("app.logs_dir").(string)}
})
// in "debug" mode, the "mailer" service is now decorated with a DebugMailer
}
The first argument is the name of the object, the second is a function that gets access to the decorated object instance and the container, and returns a new service definition.