di
di (dependency-injection) saves you from writing boiler-plate setup code in your main (and elsewhere) function by
automating the creation of functions which build the providers you want injected.
Installation
go install gitlab.com/cosban/di/v2/cmd/di@latest
Usage
- Create a
provider
file.
- Add a
go:generate
comment to provider file like so
//go:generate di generate --source=filename.go --output=filename.gen.go
If the output
flag is not specified, the generated code will be printed to stdout.
What is a provider file?
At its core, a provider is simply an exported function that delivers a specific output. This output may or may not serve
as a dependency for other providers.
A provider file serves as a central repository for defining providers and their respective prerequisites. It essentially
encapsulates functions that won't be explicitly invoked by your code.
Examples
More detailed examples are located within the [examples/|examples/] directory
//go:generate di generate --source=example.go
package example
import "fmt"
func Path() string {
return "localhost"
}
func Port() int {
return 80
}
func Address(host string, port int) string {
return fmt.Sprintf("%s:%d", host, port)
}
Using the go generate
command on example.go
would produce the following output
// Code generated by di; DO NOT EDIT.
//
// Source: example.go
// Command: di generate --source=example.go
// Version: gitlab.com/cosban/di/v2/cmd/di (devel)
package example
var singleton *ExampleComponent
func GetExampleComponent() *ExampleComponent {
if singleton == nil {
singleton = &ExampleComponent{}
}
return singleton
}
type ExampleComponent struct {
}
func (c *ExampleComponent) Path() string {
return Path()
}
func (c *ExampleComponent) Port() int {
return Port()
}
func (c *ExampleComponent) Address() string {
return Address(
c.Path(),
c.Port(),
)
}
Which could then be used in your main.go
to retrieve a correctly formatted address without having to write the
boilerplate or determine the order of which providers to create first.
package main
import "example"
func main() {
component := example.GetExampleComponent()
fmt.Printf(component.Address())
}
Troubleshooting Errors
Type |
Example Message |
Mitigation |
Missing dependencies |
di error: missing dependency detected: Dependency provider(s) for Foo() Bar are missing with the following identifiers: Baz() Qux |
Create a provider function named Baz which returns a Qux or rename the dependency to match another provider of Qux |
Circular dependency |
di error: circular dependency detected: Foo() Bar <-> Baz() Qux and Baz() Qux <-> Foo() Bar |
Modify the dependency structure between Foo and Baz to eliminate any cyclical dependencies. |
Invalid source |
di error: unable to parse source |
Fix your code. |
Features
Singleton Providers
Singleton providers are providers which are only created once and then cached for future use. They're singletons.
To create a singleton, insert a // @singleton
comment above the provider function.
// @singleton
func Bar() string {
return "bar"
}
Component Embedding
Components, the generated structures that utilize providers to acquire their dependencies, can be integrated within
provider files. This integration enables the reuse and modification of providers not only for testing purposes but also
to eliminate redundant boilerplate code and facilitate the management of complex configurations.
To embed a component, insert // @component
comment above the provider function responsible for returning a
pointer to the component.
// @component
func ExampleComponent() *example.Component {
return example.GetExampleComponent()
}
Key points to note:
- All providers from the embedded component will be available to the newly generated component.
- Provider definitions within components that share identical identifiers (name and return type) with those defined in
the provider file will be overridden by the latter.
- Multiple components may be embedded within the same provider file but errors will occur if two embedded components
share providers with identical identifiers if that provider is not overridden by the current provider file.
- The component generation process supports nested components, allowing the usage of components which themselves have
embedded other components without any additional configuration.