Documentation ¶
Overview ¶
Package inj provides reflection-based dependency injection for structs and functions. Some parts of it will be familiar to anyone who's ever used https://github.com/facebookgo/inject; others bear a passing similarity to depdendency injection in Angular.js. It's designed for medium to large applications, but it works just fine for small apps too. It's especially useful if your project is is BDD/TDD-orientated.
The essential premise of the package is that of object graphs. An object graph is just an index of objects organised by their explicitly nominated relationships. Depending on the application, a graph can be very simple - struct A depends on struct B - or extremely complicated. Package inj aims to simplify the process of creating and maintaining a graph of any complexity using out-of-the-box features of the Go language, and to povide easy access to the objects in the graph.
A simple and unrealistically trivial example is this:
package main import ( "fmt" "github.com/yourheropaul/inj" ) type ServerConfig struct { Port int `inj:""` Host string `inj:""` } func main() { config := ServerConfig{} inj.Provide(&config, 6060, "localhost") // The struct fields have now been set by the graph, and // this will print "localhost:6060" fmt.Printf("%s:%d",config.Host,config.Port) }
To understand what's happening there, you have to perform a bit of counter-intuitive reasoning. Inj has a global graph (which is optional, and can be disabled with the noglobals build tag) that is accessed through the three main API functions – inj.Provide(), inj.Assert() and inj.Inject(). The first and most fundamental of those functions - inj.Provide() - inserts anything passed to it into the graph, and then tries to wire up any dependency requirements.
Before we get on to what dependency requirements are, it's important to know how a graph works. It is, in its simplest form, a map of types to values. In the example above, the graph - after inj.Provide() is called - will have three entries:
[ServerConfig] => (a pointer to the config variable) [int] => 6060 [string] => "localhost"
There can only be one entry for each type in a graph, but since Go allows new types to be created arbitrarly, that's not very much of a problem for inj. For example, if I wanted a second string-like type to be stored in the graph, I could simply type one:
package main import ( "github.com/yourheropaul/inj" ) type MyString string func main() { var basicString string = "this is a 'normal' string" var myString MyString = "this is a typed string" inj.Provide(basicString,myString) }
In that example, the graph would now contain two separate, stringer entities:
[string] => "this is a 'normal' string" [MyString] => "this is a typed string"
Back to depdendency requirements. The ServerConfig struct above has two, indicated by the inj:"" struct field tags (advanced usage of the package makes use of values in the tags, but we can ignore that for now). At the end of the inj.Provide() call, the graph is wired up – which essentially means finding values for all of the dependency requirements by type. The Port field of the ServerConfig struct requires and int, and the graph has one, so it's assigned; the Host field requires as string, and that can be assigned from the graph too.
Obviously these examples are trivial in the extreme, and you'd probably never use the inj package in that way. The easiest way to understand the package for real-world applications is to refer to the example application: https://github.com/yourheropaul/inj/tree/master/example.
For more general information, see the Wikipedia article for a technical breakdown of dependency injection: https://en.wikipedia.org/wiki/Dependency_injection.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AddDatasource ¶
func AddDatasource(ds ...interface{}) error
Add any number of Datasources, DatasourceReaders or DatasourceWriters to the graph. Returns an error if any of the supplied arguments aren't one of the accepted types.
Once added, the datasources will be active immediately, and the graph will automatically re-Provide itself, so that any depdendencies that can only be met by an external datasource will be wired up automatically.
func Assert ¶
Make sure that all provided dependencies have their requirements met, and return a list of errors if they haven't. A graph is never really finalised, so Provide() and Assert() can be called any number of times.
func Inject ¶
func Inject(fn interface{}, args ...interface{})
Given a function, call it with arguments assigned from the graph. Additional arguments can be provided for the sake of utility.
Inject() will panic if the provided argument isn't a function, or if the provided function accepts variadic arguments (because that's not currently supported in the scope of inj).
func Provide ¶
func Provide(inputs ...interface{}) error
Insert zero or more objected into the graph, and then attempt to wire up any unmet dependencies in the graph.
As explained in the main documentation (https://godoc.org/github.com/yourheropaul/inj), a graph consists of what is essentially a map of types to values. If the same type is provided twice with different values, the *last* value will be stored in the graph.
func SetGrapher ¶
func SetGrapher(g Grapher)
Set a specific grapher instance, which will replace the global graph.
Types ¶
type Datasource ¶
type Datasource interface { DatasourceReader DatasourceWriter }
A Datasource is any external interface that provides an interface given a string key. It's split into two fundamental components: the DatasourceReader and the DatasourceWriter. For the purposes of automatic configuration via dependency injection, a DatasourceReader is all that's required.
Datasource paths are supplied by values in structfield tags (which are empty for normal inj operation). Here's an example of a struct that will try to fulfil its dependencies from a datasource:
type MyStruct stuct { SomeIntVal int `inj:"some.path.in.a.datasource"` }
When trying to connect the dependency on an instance of the struct above, inj will poll any available DatasourceReaders in the graph by calling their Read() function with the string argument "some.path.in.a.datasource". If the function doesn't return an error, then the resultant value will be used for the dependency injection.
A struct tag may contain multiple datasource paths, separated by commas. The paths will be polled in order of their appearance in the code, and the value from the first DatasourceReader that doesn't return an error on its Read() function will be used to meet the dependency.
DatasourceWriters function in a similar way: when a value is set on an instance of a struct via inj's dependency injection, any associated DatasourceWriters' Write() functions are called for each datasource path.
type DatasourceReader ¶
A datasource reader provides an object from a datasource, identified by a given string key. Refer to the documentation for the Datasource interface for more information.
type DatasourceWriter ¶
A datasource reader sends an object to a datasource, identified by a given string key. Refer to the documentation for the Datasource interface for more information.
type Grapher ¶
type Grapher interface { Provide(inputs ...interface{}) error Inject(fn interface{}, args ...interface{}) Assert() (valid bool, errors []string) AddDatasource(...interface{}) error }
A Grapher is anything that can represent an application graph
func GetGrapher ¶
func GetGrapher() Grapher
Fetch the current grapher instance (in other words, get the global graph)