container

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Dec 23, 2022 License: MIT Imports: 6 Imported by: 3

README

IoC Container

Elegant Go IoC based on Laravels container

Go Reference Go Report Card

Installation/Setup
go get github.com/envuso/go-ioc-container

You now have a global container at your disposal

Bind a service to an abstraction

package main

type SayHelloService interface {
	SayHello() string
}

func NewHelloWorldService() SayHelloService {
	return &HelloWorldService{}
}

type HelloWorldService struct{}

func (s *HelloWorldService) SayHello() string {
	return "Hello World"
}

func main() {
	// We can do it this way
	Container.Bind(new(SayHelloService), new(HelloWorldService))
	// Binding abstract -> concrete resolver function
	Container.Bind(new(SayHelloService), NewHelloWorldService)
	// Binding abstract -> concrete via single function
	Container.Bind(NewHelloWorldService)
	
	// Now we resolve...
	
	// Option one, type casts
	service := Container.Make(new(SayHelloService)).(SayHelloService)
	
	// Option two, bind to var
	var service SayHelloService
	Container.MakeTo(&service)
}
Injection

When Make/MakeTo is called, any dependencies your service requires Will be resolved from the container... So for example, you could do this

package main

func main() {
	
	// Create and bind first thing to container
	type ServiceOne struct {
		msg string
	}
	Container.Bind(func() *ServiceOne {
		return &ServiceOne{msg: "Hello there!"}
	})
	
	// Now lets set up our service which uses this
	type ServiceTwo struct {
		ServiceOne *ServiceOne
	}
	
	// Our resolver function, ServiceOne is a dependency	
	Container.Bind(new(ServiceTwo), func(serviceOne *ServiceOne) *ServiceTwo {
		return &ServiceTwo{ServiceOne: serviceOne}
	})
	
	// Now when we resolve ServiceTwo it will have an instance of ServiceOne attached
	var serviceTwo *ServiceTwo
	Container.MakeTo(&serviceTwo)
	
	print(serviceTwo.ServiceOne.msg) // outputs "Hello There!"
}
Calling methods/Creating structs

If we wish to instantiate a struct/call a method using dependency injection, we can!

Calling methods with DI
package main

// This is a nice simple way I like to boot my apps up
func main() {
	// Bind some service to the container
	Container.Bind(NewDatabaseService)
	Container.Call(bootApp)
}

func bootApp(database *DatabaseService) {
	// database will be automatically injected into the method & called for us.
}

Instantiating structs with DI
package main

type SomeOtherServiceAbstract interface {
	DoAThing()
}
type SomeOtherService struct{}

func (s *SomeOtherService) DoAThing() {
	print("Hi :)")
}

type SomeService struct {
	someOtherService *SomeOtherService
}

func main() {
	// Bind our services to the container
	Container.Bind(func() *SomeOtherService {
		return &SomeOtherService{}
	})
	Container.Bind(func() *SomeService {
		return &SomeService{}
	})
	Container.Call(bootApp)
}

// Resolve our service from the container
func bootApp(service *SomeService) {
	// service.someOtherService will now be a fresh instance of SomeOtherService
	service.someOtherService.DoAThing()
}

Features
  • Binding:
    • Abstract -> Concrete
    • Concrete
    • Abstract -> Concrete via function
    • Singletons (Container.Singleton(new(SingletonService)))
    • Singleton Instances(pre created) (Container.Instance(someVarWithInstance))
    • Tagging categories of bindings with a string (Container.Tag("SomeCategory", new(ServiceOne), new(ServiceTwo))
      • Container.Tagged("SomeCategory"))
  • Resolution:
    • Finding required args to instantiate via a function and injecting them
    • Instantiating a struct and filling its fields
  • Dependency Injection:
    • Ability to call a method via the container (Container.Call(methodReference)) - Type hinted parameters are resolved from the container(if bound)
    • Ability to instantiate a struct & fill the fields (atm, only for structs bound to the container)
      • This allows us to bind to the container, and have additional field level injection, rather than just the function we bind with
      • Struct tag & Config option to only inject to fields with the specified tag(basically complete, need to test & check some things)
  • Child Containers - (Container.CreateChildContainer())
    • If the binding isn't found in the child, it will be resolved from parents
    • Allowing for request based Containers, that then fall back to the main container
  • "Invocation" helper:
    • This is a helper I created to make calling a method/instantiating & filling struct fields a bit cleaner
      • CreateInvocable(reflect.TypeOf(method or struct) - This will give us an instance of "Invocable" back
    • More docs to come in the future... refer to Container_test.go ^^

There's a lot more to document, but it's still a WIP, just wanted to get this out today

TODO:
  • Struct field injection (if your binding has services which exist in the container, they'll be resolved and set on the struct during resolve) - I have the code for this, just need to add it
  • Ability to call a method via the container
    • Ability to call a method on a binding via the container
    • Note: This is already possible, im just unsure on the syntax I want to go with
  • Container resolution events (hook into bindings being resolved)
  • Probably lots more :D
One thing to clear up

I get it that a lot of Go programmers don't feel we need these things, or think that we shouldn't try to make things " elegant"/"framework-like". But I disagree and that's my personal opinion. We all have different ones :)

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Container = CreateContainer()
View Source
var ContainerTypes = &Types{}

Functions

func Bind added in v0.0.5

func Bind(bindingDef ...any) bool

func Call added in v0.0.5

func Call(function any, parameters ...any) []any

func ClearInstances added in v0.0.5

func ClearInstances()

func Instance added in v0.0.5

func Instance(instance any) bool

func IsBound added in v0.0.5

func IsBound(binding any) bool

func Make added in v0.0.5

func Make(abstract any, parameters ...any) any

func MakeTo added in v0.0.5

func MakeTo(makeTo any, parameters ...any)

func Reset added in v0.0.5

func Reset()

func Singleton added in v0.0.5

func Singleton(singleton any, concreteResolverFunc ...any) bool

func Tag added in v0.0.5

func Tag(tag string, bindings ...any) bool

func Tagged added in v0.0.5

func Tagged(tag string) []any

Types

type Binding added in v0.0.4

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

type ContainerConfig added in v0.0.4

type ContainerConfig struct {
	OnlyInjectStructFieldsWithInjectTag bool
}

ContainerConfig - Holds configuration values... soon I will add some more, make them work fully Right now this is a placeholder

type ContainerInstance added in v0.0.4

type ContainerInstance struct {
	Config *ContainerConfig
	// contains filtered or unexported fields
}

ContainerInstance - Holds all of our container registration

func CreateChildContainer added in v0.0.5

func CreateChildContainer() *ContainerInstance

func CreateContainer

func CreateContainer() *ContainerInstance

CreateContainer - Create a new container instance

func ParentContainer added in v0.0.5

func ParentContainer() *ContainerInstance

func (*ContainerInstance) Bind added in v0.0.4

func (container *ContainerInstance) Bind(bindingDef ...any) bool

Bind - Add a binding to the container, we can do this in a few different ways...

------

Function binding:

For example:

ContainerInstance.Bind(func() AbstractServiceInterface { return NewService() })

AbstractServiceInterface will be our resolver, NewService() is the bound type to resolve.

We can also just provide a concrete binding For example...

ContainerInstance.Bind(NewService)

------

Abstract Interface -> Concrete Implementation binding:

For example:

ContainerInstance.Bind((*AbstractServiceInterface)(nil), NewService)

Assuming the NewService func would return something like "*Service" This doesn't look great, but it's the only way I know of, to pass an interface as value

If "NewService" returns AbstractServiceInterface, you can just do

ContainerInstance.Bind(NewService)

------

Concrete binding:

For example:

ContainerInstance.Bind(SomeConcreteService{})

or

ContainerInstance.Bind(new(SomeConcreteService))

func (*ContainerInstance) Call added in v0.0.4

func (container *ContainerInstance) Call(function any, parameters ...any) []any

Call - Call the specified function via the container, you can add parameters to your function, and they will be resolved from the container, if they're registered

func (*ContainerInstance) ClearInstances added in v0.0.4

func (container *ContainerInstance) ClearInstances()

ClearInstances - This will just remove any singleton instances from the container When they are next resolved via Make/MakeTo, they will be instantiated again

func (*ContainerInstance) CreateChildContainer added in v0.0.4

func (container *ContainerInstance) CreateChildContainer() *ContainerInstance

CreateChildContainer - Returns a new container, any failed look-ups of our child container, will then be looked up in the parent, or returned nil

func (*ContainerInstance) Instance added in v0.0.4

func (container *ContainerInstance) Instance(instance any) bool

Instance - This is similar to Singleton, except with Singleton we provide a type to instantiate With instance, we provide an already instantiated value to the container

func (*ContainerInstance) IsBound added in v0.0.4

func (container *ContainerInstance) IsBound(binding any) bool

IsBound - Check if the provided value type exists in our container

func (*ContainerInstance) Make added in v0.0.4

func (container *ContainerInstance) Make(abstract any, parameters ...any) any

Make - Try to make a new instance of the provided value and return it This requires a type cast to work nicely... For example:

service := ContainerInstance.Make((*ServiceAbstract)(nil))

func (*ContainerInstance) MakeTo added in v0.0.4

func (container *ContainerInstance) MakeTo(makeTo any, parameters ...any)

MakeTo - Try to make a new instance of the provided value and assign it to your arg For example:

var service ServiceAbstract
ContainerInstance.MakeTo(&service)

func (*ContainerInstance) ParentContainer added in v0.0.4

func (container *ContainerInstance) ParentContainer() *ContainerInstance

ParentContainer - Returns the parent container, if one exists

func (*ContainerInstance) Reset added in v0.0.4

func (container *ContainerInstance) Reset()

Reset - Reset will empty all bindings in this container, you will have to register any bindings again before you can resolve them.

func (*ContainerInstance) ResolveFunctionArgs added in v0.0.5

func (container *ContainerInstance) ResolveFunctionArgs(function reflect.Value, parameters ...any) []reflect.Value

ResolveFunctionArgs - Resolves the args of our function we bound to the container parameters is an array of values that we wish to provide which is optional parameters will first be assigned starting at index 0 of the functions args Then we'll look at the function args, and if we assigned a value from the parameters already it will use that, otherwise we'll look the type up in the container and resolve it

func (*ContainerInstance) ResolveFunctionArgsWithInterceptor added in v0.0.5

func (container *ContainerInstance) ResolveFunctionArgsWithInterceptor(function reflect.Value, interceptor FuncArgResolverInterceptor, parameters ...any) []reflect.Value

func (*ContainerInstance) Singleton added in v0.0.4

func (container *ContainerInstance) Singleton(singleton any, concreteResolverFunc ...any) bool

Singleton - Bind a "class" that should only be instantiated once when resolved in the future, the initial instantiation of this type will be returned

func (*ContainerInstance) Tag added in v0.0.4

func (container *ContainerInstance) Tag(tag string, bindings ...any) bool

Tag - When we've bound to the container, we can then tag the abstracts with a string This is useful when we want to obtain a "category" of implementations

For example; Imagine we have a few different "statistic gathering" services

// Bind our individual services
Container.Bind(new(NewUserPostViewsStatService), func () {})
Container.Bind(new(NewPageViewsStatService), func () {})

// Add the services to the "StatServices" "Category"
Container.Tag("StatServices", new(NewUserPostViewsStatService), new(NewPageViewsStatService))

// Now we can obtain them all
Container.Tagged("StatServices")

func (*ContainerInstance) Tagged added in v0.0.4

func (container *ContainerInstance) Tagged(tag string) []any

Tagged - Resolve the instances from the container using the specified tag Refer to Tag to see how adding tagged bindings works

type FuncArgResolverInterceptor added in v0.0.5

type FuncArgResolverInterceptor = func(index int, argType reflect.Type, typeZeroVal reflect.Value) (reflect.Value, bool)

type Invocable added in v0.0.3

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

func CreateInvocable added in v0.0.3

func CreateInvocable(bindingType reflect.Type) *Invocable

CreateInvocable - Binding should be a struct or function

func CreateInvocableFunction added in v0.0.3

func CreateInvocableFunction(function any) *Invocable

CreateInvocableFunction - Pass a function reference through - skips the need to get/resolve the type etc

func CreateInvocableStruct added in v0.0.5

func CreateInvocableStruct(structRef any) *Invocable

CreateInvocableStruct - Pass a struct reference through - skips the need to get/resolve the type etc

func (*Invocable) CallMethodByNameWith added in v0.0.3

func (invocable *Invocable) CallMethodByNameWith(methodName string, container *ContainerInstance, parameters ...any) []reflect.Value

CallMethodByNameWith - Call the method and assign its parameters from the passed parameters & container

func (*Invocable) CallMethodByNameWithArgInterceptor added in v0.0.5

func (invocable *Invocable) CallMethodByNameWithArgInterceptor(methodName string, container *ContainerInstance, interceptor FuncArgResolverInterceptor, parameters ...any) []reflect.Value

func (*Invocable) CallMethodWith added in v0.0.3

func (invocable *Invocable) CallMethodWith(container *ContainerInstance, parameters ...any) []reflect.Value

CallMethodWith - Call the method and assign its parameters from the passed parameters & container

func (*Invocable) InstantiateStructAndFill added in v0.0.5

func (invocable *Invocable) InstantiateStructAndFill(container *ContainerInstance) reflect.Value

func (*Invocable) InstantiateWith added in v0.0.3

func (invocable *Invocable) InstantiateWith(container *ContainerInstance) any

InstantiateWith - Instantiate a struct and fill its fields with values from the container

type PkgType added in v0.0.5

type PkgType struct {
	Name string
	Path string
	// FullName - The Path + Name split by a /
	FullName string

	Type    reflect.Type
	Kind    reflect.Kind
	TypeStr string
}

func (*PkgType) Save added in v0.0.5

func (t *PkgType) Save()

type Types added in v0.0.5

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

func (*Types) Clear added in v0.0.5

func (t *Types) Clear()

func (*Types) Has added in v0.0.5

func (t *Types) Has(r any) bool

func (*Types) Of added in v0.0.5

func (t *Types) Of(typ any) *PkgType

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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