gluten

package module
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Feb 27, 2023 License: Apache-2.0 Imports: 19 Imported by: 10

README

gluten

Dependency Injection Runtime Framework for Golang inspired by Spring Framework in Java.

All injections happen on runtime and took O(n*m) complexity, where n - number of interfaces, m - number of services. In golang we have to check each interface with each instance to know if they are compatible. All injectable fields must have tag inject and be public.

Usage

Dependency Injection framework for complex applications written in Golang. There is no capability to scan components in packages provided by Golang language itself, therefore the context creation needs to see all beans as memory allocated instances by pointers. The best practices are to inject beans by interfaces between each other, but create context of their implementations.

Example:

var ctx, err = gluten.New(
    logger,
    &storageImpl{},
    &configServiceImpl{},
    &userServiceImpl{},
    &struct {
        UserService UserService `inject`  // injection based by interface or pointer 
    }{}, 
)
require.Nil(t, err)
defer ctx.Close()

Gluten Framework does not support anonymous injection fields.

Wrong:

type wrong struct {
    UserService `inject`  // since the *wrong structure also implements UserService interface it can lead to cycle and wrong injections in context
}

Right:

type right struct {
    UserService UserService `inject`  // guarantees less conflicts with method names and dependencies
}
Types

Gluten Framework supports following types for beans:

  • Pointer
  • Interface
  • Function

Gluten Framework does not support Struct type as the bean instance type.

Function

Function in golang is the first type citizen, therefore Bean Framework supports injection of functions by default.

Example:

type holder struct {
	StringArray   func() []string `inject`
}

var ctx, err = gluten.New (
    &holder{},
    func() []string { return []string {"a", "b"} },
)
require.Nil(t, err)
defer ctx.Close()
Collections

Gluten Framework supports injection of bean collections including Slice and Map. All collection injections would be treated as collection of gluten. If you need to inject collection of primitive types, please use function injection.

Example:

type holderImpl struct {
	Array   []Element          `inject`
	Map     map[string]Element `inject`
}

var ElementClass = reflect.TypeOf((*Element)(nil)).Elem()
type Element interface {
    gluten.NamedBean
    gluten.OrderedBean
}

Element should implement gluten.NamedBean interface in order to be injected to map. Bean name would be used as a key of the map. Dublicates are not allowed.

Element also can implement gluten.OrderedBean to assign the order for the bean in collection. Sorted collection would be injected. It is allowed to have sorted and unsorted beans in collection, sorted goes first.

gluten.InitializingBean

For each bean that implements InitializingBean interface, Gluten Framework invokes PostConstruct() method at the time of Construction of the bean. This functionality could be used for safe initialization.

Example:

type component struct {
    Dependency  *anotherComponent  `inject`
}

func (t *component) PostConstruct() error {
    if t.Dependency == nil {
        // for normal required dependency can not be happened, unless `optional` field declared
        return errors.New("empty dependency")
    }
    if !t.Dependency.Initialized() {
        // for normal required dependency can not be happened, unless `lazy` field declared
        return errors.New("not initialized dependency")
    }
    return t.Dependency.DoSomething()
}
gluten.DisposableBean

For each bean that implements DisposableBean interface, Gluten Framework invokes Destroy() method at the time of closing context, in reverse order how beans were initialized.

Example:

type component struct {
    Dependency  *anotherComponent  `inject`
}

func (t *component) Destroy() error {
    // guarantees that dependency still not destroyed by calling in backwards initialization order
    return t.Dependency.DoSomething()
}
gluten.NamedBean

For each bean that implements NamedBean interface, Gluten Framework will use returned bean name of calling function BeanName() instead of class name of the bean. Together with qualifier gives ability to select that bean to inject in application context.

Example:

type component struct {
}

func (t *component) BeanName() string {
    // overrides default bean name: package_name.component
    return "new_component"
}
gluten.OrderedBean

For each bean that implements OrderedBean interface, Gluten Framework invokes method BeanOrder() to determining position of the bean inside collection at the time of injection to another bean or in case of runtime lookup request.

Example:

type component struct {
}

func (t *component) BeanOrder() int {
    // created ordered bean with order 100, would be injected in Slice(Array) in this order. 
    // first comes ordered beans, rest unordered with preserved order of initialization sequence.
    return 100
}
gluten.FactoryBean

FactoryBean interface is using to create beans by application with specific dependencies and complex logic. FactoryBean can produce singleton and non-singleton gluten.

Example:

var beanConstructedClass = reflect.TypeOf((*beanConstructed)(nil))
type beanConstructed struct {
}

type factory struct {
    Dependency  *anotherComponent  `inject`
}

func (t *factory) Object() (interface{}, error) {
    if err := t.Dependency.DoSomething(); err != nil {
        return nil, err
    }
	return &beanConstructed{}, nil
}

func (t *factory) ObjectType() reflect.Type {
	return beanConstructedClass
}

func (t *factory) ObjectName() string {
	return "qualifierBeanName" // could be an empty string, used as a bean name for produced bean, usially singleton
}

func (t *factory) Singleton() bool {
	return true
}
Lazy fields

Added support for lazy fields, that defined like this: inject:"lazy".

Example:

type component struct {
    Dependency  *anotherComponent  `inject:"lazy"`
    Initialized bool
}

type anotherComponent struct {
    Dependency  *component  `inject`
    Initialized bool
}

func (t *component) PostConstruct() error {
    // all injected required fields can not be nil
    // but for lazy fields, to avoid cycle dependencies, the dependency field would be not initialized
    println(t.Dependency.Initialized) // output is false
    t.Initialized = true
}

func (t *anotherComponent) PostConstruct() error {
    // all injected required fields can not be nil and non-lazy dependency fields would be initialized
    println(t.Dependency.Initialized) // output is true
    t.Initialized = true
}
Optional fields

Added support for optional fields, that defined like this: inject:"optional".

Example:

Example:

type component struct {
    Dependency  *anotherComponent  `inject:"optional"`
}

Suppose we do not have anotherComponent in context, but would like our context to be created anyway, that is good for libraries. In this case there is a high risk of having null-pointer panic during runtime, therefore for optional dependency fields you need always check if it is not nil before use.

if t.Dependency != nil {
    t.Dependency.DoSomething()
}
Extend

Gluten Framework has method Extend to create inherited contexts whereas parent sees only own beans, extended context sees parent and own gluten. The level of lookup determines the logic how deep we search beans in parent hierarchy.

Example:

struct a {
}

parent, err := gluten.New(new(a))

struct b {
}

child, err := parent.Extend(new(b))

len(parent.Lookup("package_name.a", 0)) == 1
len(parent.Lookup("package_name.b", 0)) == 0

len(child.Lookup("package_name.a", 0)) == 1
len(child.Lookup("package_name.b", 0)) == 1

If we destroy child context, parent context still be alive.

Example:

child.Close()
// Extend method does not transfer ownership of beans from parent to child context, you would need to close parent context separatelly, after child
parent.Close()
Level

After extending context, we can end up with hierarchy of contexts, therefore we need levels in API to understand how deep we need to retrieve beans from parent contexts.

Lookup level defines how deep we will go in to beans:

  • level 0: look in the current context, if not found then look in the parent context and so on (default)
  • level 1: look only in the current context
  • level 2: look in the current context in union with the parent context
  • level 3: look in union of current, parent, parent of parent contexts
  • and so on.
  • level -1: look in union of all contexts.
Contributions

If you find a bug or issue, please create a ticket. For now no external contributions are allowed.

Documentation

Index

Constants

View Source
const DefaultLevel = 0

Variables

View Source
var BeanClass = reflect.TypeOf((*Bean)(nil)).Elem()
View Source
var ContextClass = reflect.TypeOf((*Context)(nil)).Elem()
View Source
var DefaultCloseTimeout = time.Minute
View Source
var DisposableBeanClass = reflect.TypeOf((*DisposableBean)(nil)).Elem()

* This interface uses to select objects that could free resources after closing context

View Source
var FactoryBeanClass = reflect.TypeOf((*FactoryBean)(nil)).Elem()
View Source
var InitializingBeanClass = reflect.TypeOf((*InitializingBean)(nil)).Elem()
View Source
var NamedBeanClass = reflect.TypeOf((*NamedBean)(nil)).Elem()

* This interface used to collect all beans with similar type in map, where the name is the key

View Source
var OrderedBeanClass = reflect.TypeOf((*OrderedBean)(nil)).Elem()

* This interface used to collect beans in list with specific order

View Source
var PropertiesClass = reflect.TypeOf((*Properties)(nil))
View Source
var PropertyResolverClass = reflect.TypeOf((*PropertyResolver)(nil))
View Source
var PropertySourceClass = reflect.TypeOf((*PropertySource)(nil))
View Source
var ResourceClass = reflect.TypeOf((*Resource)(nil)).Elem()

* This interface used to access the specific resource

View Source
var ResourceSourceClass = reflect.TypeOf((*ResourceSource)(nil))
View Source
var ScannerClass = reflect.TypeOf((*Scanner)(nil)).Elem()

* This interface used to provide pre-scanned instances in gluten.New method

View Source
var VerboseClass = reflect.TypeOf((*Verbose)(nil))

Functions

This section is empty.

Types

type Bean

type Bean interface {

	/**
	Returns name of the bean, that could be instance name with package or if instance implements NamedBean interface it would be result of BeanName() call.
	*/
	Name() string

	/**
	Returns real type of the bean
	*/
	Class() reflect.Type

	/**
	Returns true if bean implements interface
	*/
	Implements(ifaceType reflect.Type) bool

	/**
	Returns initialized object of the bean
	*/
	Object() interface{}

	/**
	Returns factory bean of exist only beans created by FactoryBean interface
	*/
	FactoryBean() (Bean, bool)

	/**
	Re-initialize bean by calling Destroy method if bean implements DisposableBean interface
	and then calls PostConstruct method if bean implements InitializingBean interface

	Reload can not be used for beans created by FactoryBean, since the instances are already injected
	*/
	Reload() error

	/**
	Returns current bean lifecycle
	*/
	Lifecycle() BeanLifecycle

	/**
	Returns information about the bean
	*/
	String() string
}

type BeanLifecycle

type BeanLifecycle int32
const (
	BeanAllocated BeanLifecycle = iota
	BeanCreated
	BeanConstructing
	BeanInitialized
	BeanDestroying
	BeanDestroyed
)

func (BeanLifecycle) String

func (t BeanLifecycle) String() string

type Context

type Context interface {
	/**
	Gets parent context if exist
	*/
	Parent() (Context, bool)

	/**
	New new context with additional beans based on current one
	*/
	Extend(scan ...interface{}) (Context, error)

	/**
	Destroy all beans that implement interface DisposableBean.
	*/
	Close() error

	/**
	Get list of all registered instances on creation of context with scope 'core'
	*/
	Core() []reflect.Type

	/**
	Gets obj by type, that is a pointer to the structure or interface.

	Example:
		package app
		type UserService interface {
		}

		list := ctx.Bean(reflect.TypeOf((*app.UserService)(nil)).Elem(), 0)

	Lookup level defines how deep we will go in to beans:

	level 0: look in the current context, if not found then look in the parent context and so on (default)
	level 1: look only in the current context
	level 2: look in the current context in union with the parent context
	level 3: look in union of current, parent, parent of parent contexts
	and so on.
	level -1: look in union of all contexts.
	*/
	Bean(typ reflect.Type, level int) []Bean

	/**
	Lookup registered beans in context by name.
	The name is the local package plus name of the interface, for example 'app.UserService'
	Or if bean implements NamedBean interface the name of it.

	Example:
		beans := ctx.Bean("app.UserService")
		beans := ctx.Bean("userService")

	Lookup parent context only for beans that were used in injection inside child context.
	If you need to lookup all beans, use the loop with Parent() call.
	*/
	Lookup(name string, level int) []Bean

	/**
	Inject fields in to the obj on runtime that is not part of core context.
	Does not add a new bean in to the core context, so this method is only for one-time use with scope 'runtime'.
	Does not initialize bean and does not destroy it.

	Example:
		type requestProcessor struct {
			app.UserService  `inject`
		}

		rp := new(requestProcessor)
		ctx.Inject(rp)
		required.NotNil(t, rp.UserService)
	*/
	Inject(interface{}) error

	/**
	Returns resource and true if found
	Path should come with ResourceSource name prefix.
	Uses default level of lookup for the resource.
	*/
	Resource(path string) (Resource, bool)

	/**
	Returns context placeholder properties
	*/
	Properties() Properties

	/**
	Returns information about context
	*/
	String() string
}

func New

func New(scan ...interface{}) (Context, error)

type DisposableBean

type DisposableBean interface {
	Destroy() error
}

type FactoryBean

type FactoryBean interface {

	/**
	returns an object produced by the factory, and this is the object that will be used in context, but not going to be a bean
	*/
	Object() (interface{}, error)

	/**
	returns the type of object that this FactoryBean produces
	*/
	ObjectType() reflect.Type

	/**
	returns the bean name of object that this FactoryBean produces or empty string if name not defined
	*/
	ObjectName() string

	/**
	denotes if the object produced by this FactoryBean is a singleton
	*/
	Singleton() bool
}

type InitializingBean

type InitializingBean interface {
	PostConstruct() error
}

type NamedBean

type NamedBean interface {

	/**
	Returns bean name
	*/
	BeanName() string
}

type OrderedBean

type OrderedBean interface {

	/**
	Returns bean order
	*/
	BeanOrder() int
}

type Properties

type Properties interface {
	PropertyResolver

	/**
	Register additional property resolver. It would be sorted by priority.
	*/
	Register(PropertyResolver)
	PropertyResolvers() []PropertyResolver

	/**
	Loads properties from map
	*/
	LoadMap(source map[string]interface{})

	/**
	Loads properties from input stream
	*/
	Load(reader io.Reader) error

	/**
	Saves properties to output stream
	*/
	Save(writer io.Writer) (n int, err error)

	/**
	Parsing content as an UTF-8 string
	*/
	Parse(content string) error

	/**
	Dumps all properties to UTF-8 string
	*/
	Dump() string

	/**
	Extends parent properties
	*/
	Extend(parent Properties)

	/**
	Gets length of the properties
	*/
	Len() int

	/**
	Gets all keys associated with properties
	*/
	Keys() []string

	/**
	Return copy of properties as Map
	*/
	Map() map[string]string

	/**
	Checks if property contains the key
	*/
	Contains(key string) bool

	/**
	Gets property value and true if exist
	*/
	Get(key string) (value string, ok bool)

	/**
	Additional getters with type conversion
	*/
	GetString(key, def string) string
	GetBool(key string, def bool) bool
	GetInt(key string, def int) int
	GetFloat(key string, def float32) float32
	GetDouble(key string, def float64) float64
	GetDuration(key string, def time.Duration) time.Duration
	GetFileMode(key string, def os.FileMode) os.FileMode

	// properties conversion error handler
	GetErrorHandler() func(string, error)
	SetErrorHandler(onError func(string, error))

	/**
	Sets property value
	*/
	Set(key string, value string)

	/**
	Remove property by key
	*/
	Remove(key string) bool

	/**
	Delete all properties and comments
	*/
	Clear()

	/**
	Gets comments associated with property
	*/
	GetComments(key string) []string

	/**
	Sets comments associated with property
	*/
	SetComments(key string, comments []string)

	/**
	ClearComments removes the comments for all keys.
	*/
	ClearComments()
}

func NewProperties

func NewProperties() Properties

type PropertyResolver

type PropertyResolver interface {

	/**
	Priority in property resolving, it could be lower or higher than default one.
	*/
	Priority() int

	/**
	Resolves the property
	*/
	GetProperty(key string) (value string, ok bool)
}

type PropertySource

type PropertySource struct {

	/**
	Path to the properties file with prefix name of ResourceSource as "name:path".
	*/
	Path string

	/**
	Map of properties
	*/
	Map map[string]interface{}
}

type Resource

type Resource interface {
	Open() (http.File, error)
}

type ResourceSource

type ResourceSource struct {

	/**
	Used for resource reference based on pattern "name:path"
	ResourceSource instances sharing the same name would be merge and on conflict resource names would generate errors.
	*/
	Name string

	/**
	Known paths
	*/
	AssetNames []string

	/**
	FileSystem to access or serve assets or resources
	*/
	AssetFiles http.FileSystem
}

type Scanner

type Scanner interface {

	/**
	Returns pre-scanned instances
	*/
	Beans() []interface{}
}

type Verbose

type Verbose struct {

	/**
	Use this logger to verbose
	*/
	Log *log.Logger
}

Jump to

Keyboard shortcuts

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