ioc

package module
Version: v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2019 License: MIT Imports: 7 Imported by: 0

README

Go Package - ioc GoDoc

Package ioc provides inversion of control containers, reflection helpers (GetNamedSetter, GetNamedInstance, GetNamedType) and Factory helpers (Resolve/ResolveNamed).

The ioc.Container and ioc.Values structs implement the Factory interface.

Package ioc is designed with the following goals in mind:

  • Well defined behavior (noted in comments and ensured by tests)
  • Idiomatic Go usage (within reason)
  • Cater for different use cases (e.g. ResolveNamed within Factory functions, MustResolvedNamed for web request scopes)
  • Robust runtime behavior (crash safe)
    • avoid infinite recursion on resolve
    • raise errors on configuration and behavioral errors
    • custom ioc.Error struct for diagnosing configuration and behavioral issues, containing the following metadata: calling function, file and line number, called function on package ioc, requested type and name, and an error code representing the type of error.
  • Predictable and reasonably efficient performance and memory usage (*to be ensured by benchmark tests)

Package ioc is not dependent on the net/http package.

Please note that package ioc is of production quality, but hasn't been thoroughly tested in production.

Installation

Current Version: 0.1.0
Go Version: 1.2+
go get -u github.com/shelakel/go-ioc

Documentation

See GoDoc on Github

License

This project is under the MIT License. See the LICENSE file for the full license text.

Usage

Please see GoDoc on Github and *_test.go files.

Performance

Due to the use of the reflect package, ioc.Container and ioc.Values are not well suited for temporary storage (e.g. passing state to functions on a hot path).

More benchmarks to be added during optimization.

go test -run=XXX -bench=. -benchmem=true

See reflect_cpu_prof_latest.svg for a CPU profile of the runtime reflection functions.

Benchmark Iterations Avg Alloc # Alloc
BenchmarkGetNamedSetter_Int 20000000 92.0 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedSetter_String 20000000 92.9 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedSetter_Interface 20000000 96.5 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedSetter_AnonStruct 20000000 114 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedSetter_Struct 20000000 93.3 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedSetter_DblPtr 20000000 109 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedInstance_Int 20000000 102 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedInstance_String 20000000 102 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedInstance_Interface 20000000 108 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedInstance_AnonStruct 20000000 120 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedInstance_Struct 20000000 101 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedInstance_DblPtr 20000000 120 ns/op 33 B/op 1 allocs/op
BenchmarkGetNamedType_Int 100000000 20.8 ns/op 0 B/op 0 allocs/op
BenchmarkGetNamedType_String 100000000 20.9 ns/op 0 B/op 0 allocs/op
BenchmarkGetNamedType_Interface 100000000 20.8 ns/op 0 B/op 0 allocs/op
BenchmarkGetNamedType_AnonStruct 50000000 33.4 ns/op 0 B/op 0 allocs/op
BenchmarkGetNamedType_Struct 100000000 20.9 ns/op 0 B/op 0 allocs/op
BenchmarkGetNamedType_DblPtr 50000000 33.4 ns/op 0 B/op 0 allocs/op

Preliminary benchmarking on my machine (i7-4770K, 2400MHz RAM) yielded 200 ns per Set/Get operation on ioc.Values, 400 ns per cached/singleton Resolve and 1300 ns per request to resolve via the factory function.

Tests

Tests are written using Ginkgo with the Gomega matchers.

Benchmark functions are written using the testing package.

TODO

  • This package isn't actively being worked on *
  • Improve performance - for now it is what it is due to the inherent limitations/cost of reflection in Go. This package is well suited for constructing singletons, not so much for use in a 'per request' type scenario.
  • Populate function that uses a Factory to populate struct instances via dependency injection on tagged fields. The current thinking is to support dynamic population e.g. standard ioc="constant", dynamic ioc_route="id" via ResolveNamed((*Factory),"route") -> (dynamic Factory).ResolveNamed((type), "id").
  • Improve README with Usage examples and topics.

Contributing

Contributions are welcome, especially in the area of additional tests and performance enhancements.

  1. Find an issue that bugs you / open a new one.
  2. Discuss.
  3. Branch off the develop branch, commit, test.
  4. Submit a pull request / attach the commits to the issue.

Documentation

Overview

Package ioc provides inversion of control containers and functionality.

The ioc.Container and ioc.Values structs implement the Factory interface.

Basics

The containers provided by package ioc make use of runtime reflection to detect value types and to resolve an instance by type and name.

Containers can be scoped.

Example:

type UserRepository interface {
	GetById(int64 id) (*User, error)
}

type PostgresUserRepository struct {
	db *sql.DB
}

func newPostgresUserRepository(db *sql.DB) *PostgresUserRepository {
	return &PostgresUserRepository{db: db}
}

func (repo *PostgresUserRepository) GetById(int64 id) (*User, error) {
	return nil, nil // stub
}

c := ioc.NewContainer()
// register PostgresUserRepository as UserRepository
createInstance := func(factory ioc.Factory) (interface{}, error) {
	var db *sql.DB
	// Resolve requires a non-nil pointer, but you can pass a reference to a nil pointer.
	if err := ioc.Resolve(factory, &db); err != nil {
		return nil, err
	}
	repo := newPostgresUserRepository(db)
	return repo, nil
}
implType := (*UserRepository)(nil) // must be a nil pointer
lifetime := ioc.PerContainer
c.MustRegister(createInstance, implType, lifetime)
// register the singleton *sql.DB
driverName := "postgres"
dataSourceName := "mydb"
db, err := sql.Open(driverName, dataSourceName)
if err != nil {
	panic(err)
}
c.MustRegisterInstance(db) // the instance being registered can't be a nil pointer or interface.
// tell the container to construct a UserRepository
var userRepository UserRepository
// Resolve requires a non-nil pointer, but you can pass a reference to a nil pointer.
c.MustResolve(&userRepository)

// scoped example
func ContainerMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
		scopedContainer := c.Scope()
		scopedContainer.MustSet(&w)
		scopedContainer.MustSet(r)
		// use the scopedContainer within the request scope, e.g. using gorilla
		context.Set(r, "container", scopedContainer)
	})
}

func DoSomething(w http.ResponseWriter, r *http.Request) {
	container := context.Get(r, "container").(*Container)
	var userRepository UserRepository
	container.MustGet(&userRepository)
	user, err := userRepository.GetById(1)
}

Resolving Instances

The following methods can be used to resolve instances:

- (*ioc.Values) Get/GetNamed
- (ioc.Factory) ResolveNamed
- ioc.Resolve/ioc.ResolveNamed

Resolved instances are stored in the value pointed to by v.

A non-nil pointer or a reference to a nil-pointer is required to set the value pointed to by v.

Registering Instances

The following methods can be used to register instances:

- (*ioc.Values) Set/SetNamed
- (*ioc.Container) Set/SetNamed (scoped container singleton/scope vars)
- (*ioc.Container) RegisterInstance/RegisterNamedInstance (root container singleton)

The instance being registered can't be a nil pointer or interface.

As a best practice, always pass the pointer to the instance you want to register.

Example: Register an instance of an interface type

values := NewValues()

file, _ := os.Open("my_file")
// file: *os.File
defer file.Close()
var f io.Reader = file

values.Set(f)  // type registered: *os.File (wrong!)
values.Set(&f) // type registered: io.Reader

Instance Factory Registrations

The following methods can be used to register an instance factory:

- (*ioc.Container) Register/RegisterNamed (instance factory)

An instance factory function must return a non-nil value or an error.

The value type should match the implementing type or
the implementing type must be an interface and the value must implement that interface.

The Lifetime characteristics determines how a resolved instance is reused:

- Per Container Lifetime requires that an instance is only created once per container.
- Per Scope lifetime requires that an instance is only created once per scope.
- Per Request lifetime requires that a new instance is created on every request.

Index

Constants

This section is empty.

Variables

View Source
var RecursionLimit int = 30

RecursionLimit specifies the maximum count resolve can be called for a type and name before an error is raised, to avoid infinite recursion.

A limit of 1 is sufficient for detecting infinite recursion for the Per Container and Per Scope lifetimes.

An (infinite -1) limit is required for detecting infinite recursion for the Per Request lifetime.

Due to the infinite limit required to accurately detect infinite recursion for the Per Request lifetime, you should set the RecursionLimit to a reasonable setting.

Functions

func GetNamedInstance

func GetNamedInstance(v interface{}, name string) (*reflect.Value, error)

Get the non-pointer reflect.Value of v.

GetNamedInstance is used to get value and type information for storing singleton instances on the Container.

Returns an error when:

- The value type is nil. (v was passed as nil with no type information)
- The value is a nil pointer or interface.

func GetNamedSetter

func GetNamedSetter(v interface{}, name string) (*reflect.Value, error)

Get the reflect.Value that can be used to set the value of v.

Returns an error when:

- The value type is nil. (v was passed as nil with no type information)
- The value isn't a pointer. (required to set v to the instance)
- The value is a nil pointer which can't be set. (use a pointer to a (nil) pointer instead)

func GetNamedType

func GetNamedType(v interface{}, name string) (reflect.Type, error)

Get the non-pointer reflect.Type of v.

GetNamedType is used to get the type information of implementation types.

Returns an error when:

- The value type is nil. (v was passed as nil with no type information)
- The value isn't a pointer. (enforced rule to ensure Interface types are registered properly)

func MustResolve

func MustResolve(factory Factory, instances ...interface{})

MustResolve uses a factory to resolve instances by type.

MustResolve calls Resolve(factory, instances...) and panics if an error is returned.

func MustResolveNamed

func MustResolveNamed(factory Factory, namedInstances map[string][]interface{})

MustResolveNamed uses a factory to resolve instances by type and name.

MustResolveNamed calls ResolveNamed(factory, namedInstances) and panics if an error is returned.

func Resolve

func Resolve(factory Factory, instances ...interface{}) error

Resolve uses a factory to resolve instances by type.

func ResolveNamed

func ResolveNamed(factory Factory, namedInstances map[string][]interface{}) error

ResolveNamed uses a factory to resolve instances by type and name.

Types

type Container

type Container struct {
	*Values
	// contains filtered or unexported fields
}

Container is an inversion of control container.

func NewContainer

func NewContainer() *Container

NewContainer creates a new inversion of control container.

func (*Container) MustRegister

func (c *Container) MustRegister(createInstance func(Factory) (interface{}, error), implType interface{}, lifetime Lifetime)

Register an instance factory with a specific lifetime.

MustRegister calls Register(createInstance, implType, lifetime) and panics if an error is returned.

func (*Container) MustRegisterInstance

func (c *Container) MustRegisterInstance(v interface{})

Register an instance on the root container.

MustRegisterInstance calls RegisterInstance(v) and panics if an error is returned.

func (*Container) MustRegisterNamed

func (c *Container) MustRegisterNamed(createInstance func(Factory) (interface{}, error), implType interface{}, name string, lifetime Lifetime)

Register a named instance factory with a specific lifetime.

MustRegisterNamed calls RegisterNamed(createInstance, implType, name, lifetime) and panics if an error is returned.

func (*Container) MustRegisterNamedInstance

func (c *Container) MustRegisterNamedInstance(v interface{}, name string)

Register a named instance on the root container.

MustRegisterNamedInstance calls RegisterNamedInstance(v, name) and panics if an error is returned.

func (*Container) MustResolve

func (c *Container) MustResolve(v interface{})

Resolve an instance by type.

MustResolve calls Resolve(v) and panics if an error is returned.

func (*Container) MustResolveNamed

func (c *Container) MustResolveNamed(v interface{}, name string)

Resolve a named instance by type.

MustResolveNamed calls ResolveNamed and panics if an error is returned.

func (*Container) Register

func (c *Container) Register(createInstance func(Factory) (interface{}, error), implType interface{}, lifetime Lifetime) error

Register an instance factory with a specific lifetime.

Register calls RegisterNamed(createInstance, implType, "", lifetime).

func (*Container) RegisterInstance

func (c *Container) RegisterInstance(v interface{}) error

Register an instance on the root container.

RegisterInstance calls RegisterNamedInstance(v, "").

func (*Container) RegisterNamed

func (c *Container) RegisterNamed(createInstance func(Factory) (interface{}, error), implType interface{}, name string, lifetime Lifetime) error

Register a named instance factory with a specific lifetime.

Returns an error when:

- The factory function is nil. (createInstance)
- The implementing type is nil.
- The implementing type isn't a pointer.
- The instance lifetime isn't supported. Currently only PerContainer, PerScope and PerRequest lifetimes are supported.

func (*Container) RegisterNamedInstance

func (c *Container) RegisterNamedInstance(v interface{}, name string) error

Register a named instance on the root container.

Returns an error when:

- The instance type is nil.
- The instance is a nil pointer or interface.

func (*Container) Registrations

func (c *Container) Registrations() []*Registration

Returns the registrations for the container.

func (*Container) Resolve

func (c *Container) Resolve(v interface{}) error

Resolve an instance by type.

Resolve calls c.ResolveNamed(v, "").

func (*Container) ResolveNamed

func (c *Container) ResolveNamed(v interface{}, name string) error

Resolve a named instance by type.

ResolveNamed creates a dependency resolver implementing the Factory interface, that proxies resolve calls to the Container.

The dependency resolver is passed to instance factory functions (instead of the container) and keeps track of the resolve call history for the request to detect infinite recursion.

Returns an error when:

- The value type is nil.
- The value isn't a pointer.
- The value is a nil pointer e.g. (*string)(nil) (use a pointer to a (nil) pointer instead)
- The dependency can't be resolved (not registered).
- The instance lifetime isn't supported. Currently only PerContainer, PerScope and PerRequest lifetimes are supported.
- An error was returned when (*Registration).CreateInstance was called.
- Infinite recursion is detected on a repetitive call to resolve an instance by type and name.

func (*Container) Scope

func (c *Container) Scope() *Container

Scope creates a new scoped container from the current container.

The Values of the current container are scoped and the registry inherited by the scoped container.

Scoped Values will resolve an instance from an ancestor when the current container is unable to resolve the instance by type and name.

type Error

type Error struct {
	Type      reflect.Type
	Name      string
	OtherType reflect.Type
	Code      ErrorCode
	Message   string
	Inner     error
	File      string
	LineNo    int
	Method    string
}

func (*Error) Error

func (e *Error) Error() string

type ErrorCode

type ErrorCode int

ErrorCode represents an error code for distinguishing between errors.

const (
	// ErrInstanceNotFound is raised by (*Values).GetNamed when an instance isn't found.
	ErrInstanceNotFound ErrorCode = iota
	// ErrNilType is raised by GetNamedSetter, GetNamedInstance, GetNamedType when the type of v is nil.
	// (e.g. called GetNamedType(v:nil, name:"").
	ErrNilType
	// ErrCreateInstanceNil is raised
	//   by (*Container).RegisterNamed when createInstance is nil or
	//   by (*Registration).CreateInstance when (*Registration).CreateInstanceFn is nil.
	ErrCreateInstanceNil
	// ErrCreateInstance is raised by (*Registration).CreateInstance when (*Registration).CreateInstanceFn
	// is not nil and returned an error.
	ErrCreateInstance
	// ErrUnresolvedDependency is raised by by (*dependencyResolver).ResolveNamed
	// when an instance isn't registered as a singleton or a factory function
	// and an instance can't be found on (*Container).Values.
	ErrUnresolvedDependency
	// ErrUnsupportedLifetime is raised by (*Container).RegisterNamed, (*Registration).CreateInstance
	// when the lifetime isn't supported.
	ErrUnsupportedLifetime
	// ErrUnexpectedValueType is raised by (*Registration).CreateInstance
	// when the type of the created instance doesn't match the registration type.
	ErrUnexpectedValueType
	// ErrInterfaceNotImplemented is raised by (*Registration).CreateInstance
	// when the registration type is an interface and the created instance type
	// doesn't implement the interface.
	ErrInterfaceNotImplemented
	// ErrNilValue is raised by GetNamedInstance when v is nil or a pointer to a nil value.
	ErrNilValue
	// ErrNonSetNilPointer is raised by GetNamedSetter when v is a nil pointer.
	ErrNonSetNilPointer
	// ErrRequirePointer is raised by GetNamedSetter, GetNamedType when v isn't a pointer.
	ErrRequirePointer
	// ErrResolveInfiniteRecursion is raised by (*dependencyResolver).ResolveNamed
	// when the count of resolve by type and name within a (*Container).ResolveNamed call
	// exceeds the RecursionLimit.
	ErrResolveInfiniteRecursion
)

type Factory

type Factory interface {
	// Resolve a named instance by type.
	ResolveNamed(v interface{}, name string) error
}

Factory represents a container able to resolve instances by type and name.

Implemented by:

- Values
- Container
- dependencyResolver (internal)

type Lifetime

type Lifetime int

Lifetime represents the lifetime characteristics of an instance.

const (
	// Per Container Lifetime requires that an instance is only created once per container.
	PerContainer Lifetime = iota
	// Per Scope lifetime requires that an instance is only created once per scope.
	PerScope
	// Per Request lifetime requires that a new instance is created on every request.
	PerRequest
)

func (Lifetime) String

func (lifetime Lifetime) String() string

type Registration

type Registration struct {
	Type             reflect.Type
	Name             string
	Value            interface{}
	CreateInstanceFn func(Factory) (interface{}, error)
	Lifetime         Lifetime
}

Registration contains the information necessary to construct an instance.

func (*Registration) CreateInstance

func (r *Registration) CreateInstance(factory Factory) (*reflect.Value, error)

CreateInstance creates an instance using the factory function.

Returns an error when:

- The factory function is nil or returns an error. (Registration.CreateInstanceFn)
- The created instance type is nil. (no type information)
- The created instance is a nil pointer or interface.
- The created instance type doesn't match the registration type or
- The implementation type is an interface and the created instance doesn't implement the interface.

type Values

type Values struct {
	// contains filtered or unexported fields
}

Values is a thread safe type-name-instance container.

func NewValues

func NewValues() *Values

NewValues creates a new Values struct.

func NewValuesScope

func NewValuesScope(parent *Values) *Values

NewValuesScope creates a new scoped Values struct with a reference to a parent Values struct.

Get calls will check the ancestors to resolve the instance by type and name.

func (*Values) Get

func (values *Values) Get(v interface{}) error

Get an instance by type.

Get calls GetNamed(v, "").

func (*Values) GetNamed

func (values *Values) GetNamed(v interface{}, name string) error

Get a named instance by type.

GetNamed calls GetNamedSetter(v, name).

Returns an error when:

- The value type is nil. (v was passed as nil with no type information)
- The value isn't a pointer. (required to set v to the instance)
- The value is a nil pointer which can't be set. (use a pointer to a (nil) pointer instead)
- The instance isn't found.

func (*Values) MustGet

func (values *Values) MustGet(v interface{})

Get an instance by type.

MustGet calls Get(v) and panics if an error is returned.

func (*Values) MustGetNamed

func (values *Values) MustGetNamed(v interface{}, name string)

Get a named instance by type.

MustGetNamed calls GetNamed(v, name) and panics if an error is returned.

func (*Values) MustSet

func (values *Values) MustSet(v interface{})

Set an instance by type.

MustSet calls Set(v) and panics if an error is returned.

func (*Values) MustSetNamed

func (values *Values) MustSetNamed(v interface{}, name string)

Set a named instance by type.

MustSetNamed calls SetNamed(v, name) and panics if an error is returned.

func (*Values) ResolveNamed

func (values *Values) ResolveNamed(v interface{}, name string) error

Resolve a named instance by type.

ResolveNamed calls GetNamed(v, name).

func (*Values) Set

func (values *Values) Set(v interface{}) error

Set an instance by type.

Set calls SetNamed(v, "").

func (*Values) SetNamed

func (values *Values) SetNamed(v interface{}, name string) error

Set a named instance by type.

SetNamed calls GetNamedInstance(v, name).

Returns an error when:

- The value type is nil. (v was passed as nil with no type information)
- The value is nil.		 (interface or pointer)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
t or T : Toggle theme light dark auto
y or Y : Canonical URL