inject

package module
v0.0.0-...-86d2a96 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2019 License: Apache-2.0 Imports: 4 Imported by: 6

README

Inject

Dependency injection library for Go (golang)

GoDoc

Build Status

Why use Inject?

Like most other golang injection frameworks, Inject allows auto-resolving dependencies by type.

Unlike most other golang injection frameworks, Inject uses constructor functions and argument pointers to allow resolving any object, without it having to be designed for injection.

This has several advantages:

  • Constructor arguments can be resolved automatically by type OR manually by unresolved pointer.
  • Public access to the struct is not required, if they implement a public interface.
  • No coordination is required between implementations of a common interface.
  • Pointers can be used to distinguish between different implementations of the same interface.
  • Pointers can even be used to distinguish between different instances of the same struct.

Use Cases

Most small applications don't need dependency injection. If you're going to use all the instances you need to construct, then you might as well just construct them and use them imperatively.

The cases where DI becomes useful usually have one or more of the following characteristics.

  1. DRYing up dependency construction
  2. Multiple consumers or controllers with overlapping, but not identical, dependency sets
  3. Test code that uses the same dependency setup as the non-test code
  4. Replacing a subset of the dependencies with Mocks, without having to rewrite the non-mock setup

Examples

  1. Example Server - Example web server with dependency injected controllers & repositories

Usage

package yours

import (
  "path/to/your/pkgA"
  "path/to/your/pkgB"
  "github.com/karlkfi/inject"
)

func main() {
	graph := inject.NewGraph()

	var (
		primitive = "some string"
		a    pkgA.InterfaceA
		b    *pkgB.StructB
	)

	// define how to construct a (dependent on b)
	graph.Define(&a, inject.NewProvider(pkgA.NewA, &b))

	// define how to construct b (not dependent on the graph)
	graph.Define(&b, inject.NewProvider(pkgB.NewB, &primitive))

	// resolve a and all its (transitive) dependencies
	graph.Resolve(&a)

    // a is now usable
	a.DoStuff()
}

The simple example above makes use of the normal providers, which require the user to specify all constructor arguments.

The even simpler example below demonstrates auto-providers, which attempt to resolve constructor arguments by only their type. This method is simpler to use, but has the drawback that all auto-resolved types must have one and only one pointer in the graph that is assignable to that type. Auto-provider usage can be mixed with normal provider usage for increased flexibility.

...

func main() {
	graph := inject.NewGraph()

	var (
		primitive = "some string"
		a    pkgA.InterfaceA
		b    *pkgB.StructB
	)

	// define how to construct a (dependent on anything that implements InterfaceB)
	graph.Define(&a, inject.NewAutoProvider(pkgA.NewA))

	// define how to construct b (no dependencies)
	graph.Define(&b, inject.NewProvider(pkgB.NewB))

	// resolve a and its dependency b (because *pkgB.StructB is assignable to InterfaceB)
	graph.Resolve(&a)

    // a is now usable
	a.DoStuff()
}

You CAN resolve everything in the graph, using graph.ResolveAll(), but you can also share a graph between multiple code paths (like a controller with multiple endpoints, or a command with multiple sub-commands) and only resolve the dependencies you need using graph.Resolve(&ptr).

Because the definitions are uniquely keyed by pointer, you can also share code that produces a general graph, and override individual definitions with more specific providers (like tests that replace a few concrete impls with mocks).

Alternate Usage

Because the API is flexible, there are several ways to do the same thing. Here's another way to define the dependency graph:

func main() {
	var (
		primitive = "some string"
		a    pkgA.InterfaceA
		b    *pkgB.StructB
	)

    // define the pointer-provider relationships during graph construction
	graph := inject.NewGraph(
		inject.NewDefinition(&a, inject.NewProvider(pkgA.NewA, &b)),
		inject.NewDefinition(&b, inject.NewProvider(pkgB.NewB, &primitive)),
	)

	// resolve a and all its (transitive) dependencies
	graph.Resolve(&a)

    // a is now usable
	a.DoStuff()
}

Object Lifecycle

Definitions that point to structs (or struct pointers or interfaces) that implement a lifcycle interface (Initializable or Finalizable) have special behavior.

When the definition is first resolved, after the provider is called to return the value, the resolver will also call the Initialize() method on the value, if it has one.

When the definition is later obscured (on graph.Finalize()), after the defined pointer is zeroed, the obscurer will also call the Finalize() method on the resolved value, if it has one.

These lifecycle methods are optional, but may be useful for opening/closing objects or starting/stopping goroutines.

Resolving/initializing is lazily performed, either when the user calls graph.Resolve() or when another resolution causes transitive resolution of its dependencies (ex: provider arguments).

Obscuring/finalizing is performed on all resolved definitions when the user calls graph.Finalize(). If you use any Finalizable objects, you will need to make sure that graph.Finalize() is called before the program exits.

Installation

To install Inject, use go get:

go get github.com/karlkfi/inject

Updating

To update Inject, use go get -u:

go get -u github.com/karlkfi/inject

Dependencies

Inject has no runtime dependencies. Tests depend on Gomega.

Testing

Tests depend on Gomega.

To install Gomega, use go get:

go get github.com/onsi/gomega

To run Inject tests, use go test:

go test github.com/karlkfi/inject/test

License

Copyright 2015 Karl Isenberg

Licensed under the Apache License Version 2.0 (the "License"); you may not use this project except in compliance with the License.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Documentation

Overview

Package inject provides a dependency injection library that uses constructor functions to define dependency relationships.

Source can be found at https://github.com/karlkfi/inject

Author: Karl Isenberg

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExtractAssignable

func ExtractAssignable(g Graph, ptr interface{}) reflect.Value

ExtractAssignable resolves a pointer into a value by finding exactly one defined pointer with an assignable type

func ExtractByType

func ExtractByType(g Graph, ptr interface{}) reflect.Value

ExtractByType resolves a pointer into a value by finding exactly one defined pointer with the specified type

func FindAssignable

func FindAssignable(g Graph, listPtr interface{}) []reflect.Value

FindAssignable resolves all defined pointers that are assignable to the type of the supplied slice and appends the resolved values to the slice.

func FindByType

func FindByType(g Graph, listPtr interface{}) []reflect.Value

FindByType resolves all defined pointers that match the type of the supplied slice and appends the resolved values to the slice.

Types

type Definition

type Definition interface {
	Ptr() interface{}
	Resolve(Graph) reflect.Value
	Obscure(g Graph)
	fmt.Stringer
}

func NewDefinition

func NewDefinition(ptr interface{}, provider Provider) Definition

type Finalizable

type Finalizable interface {
	Finalize()
}

Finalizable describes an object that needs finalization before being destroyed

type Graph

type Graph interface {
	Finalizable
	Add(Definition)
	Define(ptr interface{}, provider Provider) Definition
	Resolve(ptr interface{}) reflect.Value
	ResolveByType(ptrType reflect.Type) []reflect.Value
	ResolveByAssignableType(ptrType reflect.Type) []reflect.Value
	ResolveAll() []reflect.Value
	fmt.Stringer
}

Graph describes a dependency graph that resolves nodes using well defined relationships. These relationships are defined with node pointers and Providers.

func NewGraph

func NewGraph(defs ...Definition) Graph

NewGraph constructs a new Graph, initializing the provider and value maps.

type Initializable

type Initializable interface {
	Initialize()
}

Initializable describes an object that needs initialization after being created

type Provider

type Provider interface {
	ReturnType() reflect.Type
	Provide(Graph) reflect.Value
	fmt.Stringer
}

Provider describes how to retrieve (or construct) a generic value, given a dependency graph.

func NewAutoProvider

func NewAutoProvider(constructor interface{}) Provider

NewAutoProvider specifies how to construct a value given its constructor function. Argument values are auto-resolved by type.

func NewProvider

func NewProvider(constructor interface{}, argPtrs ...interface{}) Provider

NewProvider specifies how to construct a value given its constructor function and argument pointers

Directories

Path Synopsis
test

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL