diff --git a/api.go b/api.go index ce4ba3f..2b66221 100644 --- a/api.go +++ b/api.go @@ -21,12 +21,12 @@ type Grapher interface { // A default grapher to use in the public API var graph Grapher = NewGraph() -// Fetch the current grapher instance +// Fetch the current grapher instance (in other words, get the global graph) func GetGrapher() Grapher { return graph } -// Set a specific grapher instance +// Set a specific grapher instance, which will replace the global graph. func SetGrapher(g Grapher) { graph = g } @@ -35,8 +35,12 @@ func SetGrapher(g Grapher) { // Public API ////////////////////////////////////////////// -// Insert a set of arbitrary objects into the -// application graph +// 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 Provide(inputs ...interface{}) error { return graph.Provide(inputs...) } @@ -44,18 +48,30 @@ func Provide(inputs ...interface{}) error { // 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 Inject(fn interface{}, args ...interface{}) { graph.Inject(fn, args...) } // Make sure that all provided dependencies have their // requirements met, and return a list of errors if they -// don't. +// haven't. A graph is never really finalised, so Provide() and +// Assert() can be called any number of times. func Assert() (valid bool, errors []string) { return graph.Assert() } -// Add zero or more datasources to the global graph +// 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 AddDatasource(ds ...interface{}) error { return graph.AddDatasource(ds...) } diff --git a/documentation.go b/documentation.go new file mode 100644 index 0000000..62950b6 --- /dev/null +++ b/documentation.go @@ -0,0 +1,79 @@ +// 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. +// +package inj diff --git a/graph_assert.go b/graph_assert.go index c0cb24e..15ae9c5 100644 --- a/graph_assert.go +++ b/graph_assert.go @@ -1,5 +1,9 @@ package inj +// 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 (g *Graph) Assert() (valid bool, errors []string) { valid = true diff --git a/graph_inject.go b/graph_inject.go index a8e76b4..17d5487 100644 --- a/graph_inject.go +++ b/graph_inject.go @@ -6,7 +6,7 @@ import ( ) // Given a function, call it with arguments from the graph. -// Throws a runtime erro,r in the form of a panic, on failure. +// Throws a runtime error in the form of a panic on failure. func (g *Graph) Inject(fn interface{}, args ...interface{}) { // Reflect the input diff --git a/graph_provide.go b/graph_provide.go index d92d8b5..1badb00 100644 --- a/graph_provide.go +++ b/graph_provide.go @@ -2,6 +2,12 @@ package inj import "reflect" +// 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 (g *Graph) Provide(inputs ...interface{}) error { for _, input := range inputs { diff --git a/readme.markdown b/readme.markdown index 4c4bdee..1fff959 100644 --- a/readme.markdown +++ b/readme.markdown @@ -3,7 +3,7 @@ Are you troubled by dependency configuration issues in the middle of the night? Do you experience feelings of dread at the code boundaries of your application or during functional testing? Have you or your team ever used a global variable, exported package-level object or a build constraint hack? If the answer is *yes*, then don't wait another minute. Pick up your terminal and `go get github.com/yourheropaul/inj` today. -Come on and inject yourself before you wreck yourself. +Inject yourself before you wreck yourself. ### What *is* this thing? @@ -15,7 +15,39 @@ Come on and inject yourself before you wreck yourself. ### How do I use it? -Check out the [example application](https://github.com/yourheropaul/inj/tree/master/example) in this repository. The API is small, and everything is demonstrated there. Technical documentation is also available on [godoc.org](https://godoc.org/github.com/yourheropaul/inj). +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) +} +``` +There's a full explanation for this basic example in the [Godoc](http://localhost:6061/pkg/github.com/yourheropaul/inj/#Overview). +Obviously this example is trivial in the extreme, and you'd probably never use the the package in that way. The easiest way to understand + `inj` for real-world applications is to refer to the [example application](https://github.com/yourheropaul/inj/tree/master/example) in this repository. The API is small, and everything in the core API is demonstrated there. +### Dependency injection is great and everything, but I really want to be able to pull data directly from external services, not just the object graph. + +You mean you want to read from a JSON or TOML config file, and inject the values into Go objects directly? Maybe you'd like to pull values from a DynamoDB instance and insert them into Go struct instances with almost zero code overhead? + +That's what's `inj` is designed for! And what's more, intrepid programmer Adrian Duke has already done the leg work for you in his fantastic [configr](https://github.com/adrianduke/configr) package – see his readme for brief instructions. ### I want absolutely, positively no globals in my application. None. Can I do that with this package? @@ -35,4 +67,5 @@ Finally, `inj.Provide()` is fairly slow, but it's designed to executed at runtim ### But how do I use it? -Seriously? I just explained that a minute ago. +Seriously? I just explained that a minute ago. Maybe look at the [example application](https://github.com/yourheropaul/inj/tree/master/example) or the [Godoc](http://localhost:6061/pkg/github.com/yourheropaul/inj/#Overview). +