container

package module
v0.7.3 Latest Latest
Warning

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

Go to latest
Published: Dec 27, 2024 License: Apache-2.0 Imports: 6 Imported by: 0

README

container

gopher-container

Flexible runtime dependency container inspired on go.uber.org/dig and based on reflection. It applies the dependency tree concept to make flexible injections.

Require

  • Go >= 1.20

Install

go get github.com/Drafteame/container@latest

Usage

Using the global container you can access to all the container methods to manage dependency factories.

Inline way
package main

import (
	"github.com/Drafteame/container"
)

type param struct {}

type someType struct{
	p *param
}

func (*someType) SayHello() {
	println("hello")
}

func (*someType) SayGoodBye() {
	println("good bye")
}

type mainInterface interface{
	SayHello()
	SayGoodBye()
}

type subInterface interface {
	SayHello()
}

func someConstructor(p *param) *someType {
	return &someType{p: p}
}

func regularInstance() *someType {
	err := container.Register("someName", someConstructor, container.Inject("someParam"))
	if err != nil {
		panic(err)
	}
	
	return container.MustGet[*someType]("someName")
}

func singletonInstance() mainInterface {
	err := container.Singleton("someNameSingleton", someConstructor, container.Inject("someParam"))
	if err != nil {
		panic(err)
	}

	return container.MustGet[mainInterface]("someNameSingleton")
}

func getSingletonAsSubInterface() subInterface {
	return container.MustGet[subInterface]("someNameSingleton")
}
Functional way
package main

import (
	"github.com/Drafteame/container"
)

type param struct {}

type someType struct{
	p *param
}

func (*someType) SayHello() {
	println("hello")
}

func (*someType) SayGoodBye() {
	println("good bye")
}

type mainInterface interface{
	SayHello()
	SayGoodBye()
}

type subInterface interface {
	SayHello()
}

func someConstructor(p *param) *someType {
	return &someType{p: p}
}

func regularInstance() *someType {
	err := container.Register("someName", func() *someType {
		p := container.MustGet[*param]("someParam")
		return someConstructor(p)
    })
	
	if err != nil {
		panic(err)
	}
	
	return container.MustGet[*someType]("someName")
}

func singletonInstance() mainInterface {
	err := container.Singleton("someNameSingleton", func() *someType {
		p := container.MustGet[*param]("someParam")
		return someConstructor(p)
	})
	
	if err != nil {
		panic(err)
	}

	return container.MustGet[mainInterface]("someNameSingleton")
}

func getSingletonAsSubInterface() subInterface {
	return container.MustGet[subInterface]("someNameSingleton")
}

Dependencies

There two types of dependencies, regular dependencies and singleton dependencies.

Regular dependencies are instances that each time that are required to be injected or retrieved, they will create a new instance from the provided factory each time. This means that with this type of dependencies, you will have multiple instances of the same type and this will not share any context. Basically is a fresh new instance each time we inject it.

package main

import (
	"fmt"
	"github.com/Drafteame/container"
	"github.com/Drafteame/container/dependency"
)

type User struct {
	Name string
	Age  int
}

func newUser(name string, age int) *User {
	return &User{
		Age:  age,
		Name: name,
	}
}

func main() {
	depName := "someDep"
	dep := dependency.New(newUser, "John", 21)

	if err := container.Register(depName, dep); err != nil {
		panic(err)
	}

	userInstance, err := container.Get[*User](depName)
	if err != nil {
		panic(err)
	}

	fmt.Println(userInstance)
}

Singleton dependencies are pretty much the same as a regular dependency with the particularity that the container will keep the result obtained from the factory internally and if a new instance of the same dependency is called to be injected, instead of create a new one from the factory will inject the previous created instance.

Keep in mind that this can not work as a real singleton if the returned value of the factory is not a pointer or interface.

package main

import (
	"fmt"
	
	"github.com/Drafteame/container"
	"github.com/Drafteame/container/dependency"
)

type User struct {
	Name string
	Age  int
}

func newUser(name string, age int) *User {
	return &User{
		Age:  age,
		Name: name,
	}
}

func main() {
	depName := "someDep"
	dep := dependency.NewSingleton(newUser, "John", 21)

	if err := container.Register(depName, dep); err != nil {
		panic(err)
	}

	userInstance, err := container.Get[*User](depName)
	if err != nil {
		panic(err)
	}
	
	userInstance2, err := container.Get[*User](depName)
	if err != nil {
		panic(err)
    }
	
	if userInstance == userInstance2 {
		fmt.Println("same instance")	
    }
}

Arguments of the regular and singleton dependencies can be plain values, other dependency.Dependency objects or dependency.Injectable instances. This last type of argument are objects that make reference to a dependency that was registered in the container previously. This is specially helpful if you do not want to redefine a dependency many times, and just reuse same specification of the dependency.

Example of plain values as dependency arguments:

package main

import "github.com/Drafteame/container/dependency"

func main() {
	name := "foo"
	age := 21

	// Regular dependency
	depName := "test"
	dep := dependency.New(newUser, name, age)

	// Singleton dependency
	depName2 := "test2"
	dep2 := dependency.NewSingleton(newUser, name, age)
}

Example of dependency instances as arguments:

package main

import (
	"os"
	
	"github.com/Drafteame/container/dependency"
)

func main() {
	driver := dependency.New(newDB, os.Getenv("DB_URL"))

	// Regular dependency
	dep := dependency.New(newUser, driver)

	// Singleton dependency
	dep2 := dependency.NewSingleton(newUser, driver)
}

Example of Injectable dependency as argument:

package main

import (
	"os"

	"github.com/Drafteame/container"
	"github.com/Drafteame/container/dependency"
)

func main() {
	driverName := "database"
	driver := dependency.New(dbConstructor, os.Getenv("DB_URL"))

	if err := container.Register(driverName, driver); err != nil {
		panic(err)
    }
	
	// Regular dependency
	dep := dependency.New(userConstructor, dependency.Inject(driverName))

	// Singleton dependency
	dep2 := dependency.NewSingleton(userConstructor, dependency.Inject(driverName))
}
Invoke

There is a method that can help you to bring some extra functionality to the container and obtain more than one instance at a time.

This method will receive a callback that can or not return an error and can or not receive multiple arguments. This arguments should be structs, defining on his fields the instances that the container should inject to it.

package main

import (
	"errors"
	"fmt"
	"os"

	"github.com/Drafteame/container"
	"github.com/Drafteame/container/dependency"
	"github.com/Drafteame/container/types"
)

type args struct {
	types.In
	User *user `inject:"name=user"`
}

func invoker(in args) error {
	if in.User == nil {
		return errors.New("empty instance of user")
	}

	fmt.Println("Hello ", in.User.GetName())
	return nil
}

func main() {
	driverName := "database"
	driver := dependency.New(newDB, os.Getenv("DB_URL"))

	if err := container.Register(driverName, driver); err != nil {
		panic(err)
	}

	depName := "user"
	dep := dependency.New(newUser, dependency.Inject(driverName))

	if err := container.Register(depName, dep); err != nil {
		panic(err)
	}
	
	if err := container.Invoke(invoker); err != nil {
		panic(err)
    }
}

Also you can use interface segregation to define the arguments:

package main

import (
	"errors"
	"fmt"
	"os"

	// ...
	
	"github.com/Drafteame/container"
)

type namer interface{
	GetName()
}

type args struct {
	types.In
	User namer `inject:"name=user"`
}

func invoker(in args) error {
	if in.User == nil {
		return errors.New("empty instance of user")
	}

	fmt.Println("Hello ", in.User.GetName())
	return nil
}

func main() {
	// .....

	if err := container.Invoke(invoker); err != nil {
		panic(err)
	}
}
Optional arguments

When you define In structs to be used with the Invoke method you can mark optional fields if you expect that some fields can or not be filled by the injector and avoid an error if there's no dependency registered with the required name.

package main

import (
	"errors"
	"fmt"
	"os"

	// ...
	
	"github.com/Drafteame/container/types"
)

type namer interface{
	GetName()
}

type args struct {
	types.In
	User namer `inject:"name=notExist,optional"`
}

func invoker(in args) error {
	if in.User != nil {
		fmt.Println("Hello ", in.User.GetName())
	} else {
		fmt.Println("Ups no namer instance found")
    }
	
	return nil
}

func main() {
	// .....

	if err := container.Invoke(invoker); err != nil {
		panic(err)
	}
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrFactoryNotFunction = errors.New("factory parameter should be a function or a dependency.Dependency instance")
)

Functions

func Flush

func Flush()

Flush WARNING: This function will delete all saved instances, solved and registered factories from the container. Do not use this method on production, and just use it for testing purposes.

func Get

func Get[T any, K symbolName](name K) (T, error)

Get is a wrapper over the Get function attached to the global container. This function modify the return type of the resolved dependency, returned as `any` to the provided generic type `T`. If it can't be casted it will return an error.

func Inject

func Inject[T symbolName](name T) dependency.Injectable

Inject is a Wrapper ver the dependency.Inject function to generify string symbol name.

func Invoke

func Invoke(construct any) error

Invoke Is the entry point to execute dependency injection resolution. It calls an invoker function that can receive or not a struct that embeds inject.In struct as input, and return an error or not (any other return field or type will be ignored on resolution). When invoker is called it will resolve the dependency threes of each field from the previously provided resources on Container.

func MustGet

func MustGet[T any, K symbolName](name K) T

MustGet Same functionality that Get function but instead of returning error, it panics.

func Override added in v0.7.2

func Override[T symbolName](name T, factory any, args ...any) error

Override Set a new dependency that replaces the old one to change behavior on runtime. WARNING: This function will remove a specific factory and its solve dependency from the container. Do not use this method on production, and just use it on testing purposes.

func Register

func Register[T symbolName](name T, factory any, args ...any) error

Register It adds a new injection dependency to the container, getting the first result type of the constructor to associate the constructor on the injection dependency threes.

This injection will be resolved and built on execution time when the `inject.Invoke(...)` or `inject.Get(name)` methods are called.

func Remove added in v0.4.0

func Remove[T symbolName](name T)

Remove WARNING: This function will remove a specific factory and its solve dependency from the container. Do not use this method on production, and just use it on testing purposes.

func Singleton

func Singleton[T symbolName](name T, factory any, args ...any) error

Singleton It adds a new injection dependency to the container, getting the first result type of the constructor to associate the constructor on the injection dependency threes as a singleton instance.

This function also receive dependency arguments as variadic in case the factory were a function instead of a dependency.Dependency.

func TestMode added in v0.5.0

func TestMode()

TestMode WARNING: Sets testMode flag to true, bypassing singleton instance generation to avoid race conditions when container is used on test cases.

Types

type Container

type Container interface {
	Provide(name types.Symbol, dep dependency.Dependency) error
	Override(name types.Symbol, dep dependency.Dependency) error
	Invoke(construct any) error
	Get(name types.Symbol) (any, error)
	Flush()
	Remove(name types.Symbol)
	TestMode()
}

Container represents a dependency container that should register factory methods and its dependency threes to be injected when

func New

func New() Container

New Return a new isolated instance for the dependency injection container. This instance is totally different from the global container and do not share any saved dependency three between each other.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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