Skip to content

dgawlik/go-ioc

Repository files navigation

Go Report Card Go Reference codecov License: GPL v2 Tag

goioc - lightweight ioc container for go

Coming from Java background I was used to Spring to inject dependencies in large projects, so thought I will also use some container in Go. There are many other packages serving the same purpose, but this one has api (design) closest to my personal preferences. It also is so small that you can read the source code in coffee break.

Features

  • injection by type definition
  • injection of any type
  • dependencies computed on demand
  • caching of computed dependencies
  • properties attached per container
  • minimal codebase

Installation

To install this package, run the following command in your project directory.

go get github.com/dgawlik/go-ioc

Use it like so:

import "github.com/dgawlik/go-ioc"

Api

func Resolve[T any](forceRebind bool) (T, error) 

Returns fully injected value bound to type T. On consecutive calls and forceRebind false, value from cache is taken. forceRebind forces to create new injected values anyway and overwrite cache.

func InjectResolve[T any](ctor any, forceRebind bool) (T, error) 

Returns value injected on the fly by provided constructor. The value is never put to cache.

func Bind[T any](value any) error

Associates value with type T. The value is taken as is from the function and put to cache.

func InjectBind[T any](value any, isPrototype bool) error

Expects curried function to be provided. Outer function's parameters are values to be injected. During resolve this function is called to produce proper value. If prototype flag is set, it is never put to cache.

func SetProperty(key string, value any) 

Attaches some value to key container-wise. You can think of it as containers metadata. Then you can inject Properties built-in type to recover values.

func NewContainer() *Container 

Creates new container with fresh state and no bindings.

func SetContainer(newC *Container)

Sets default container to this one.

Design

The container in fact is currying on steroids. Had you used plain technique of curried functions you would have to pass dependencies all the way down the composed objects. In contrast, the container does this for you and alll you have to do is to only provide direct dependencies. The injection is not limited to functions any type can be registered and injected (but remember to declare type alias!).

There are no scopes in the container familiar to Spring, but prototype and singleton scopes can easily be emulated. The container looks up dependencies when you try to retrive value by type. If everything works the intermediate results are stored in cache. Consecutive calls return same values. Not however, when you pass forceRebind true, then cache is omitted in computations and refreshed.

Examples

Example on general usage

package main

import (
	"fmt"
	"math"

	goioc "github.com/dgawlik/go-ioc"
)

type IsPrime func(num int) bool

type Greeter func(name string, age int)

func main() {

	goioc.Bind[IsPrime](func(num int) bool {
		if num < 2 {
			return false
		}
		sq_root := int(math.Sqrt(float64(num)))
		for i := 2; i <= sq_root; i++ {
			if num%i == 0 {
				return false
			}
		}
		return true
	})

	greeter, _ := goioc.InjectResolve[Greeter](func(isPrime IsPrime) func(name string, age int) {
		return func(name string, age int) {
			statement := "is not"
			if isPrime(age) {
				statement = "is"
			}

			fmt.Printf("Hello %s, your age %s prime.\n", name, statement)
		}
	}, false)

	greeter("Dominik", 33)
}

Short sample how properties can interact with injections.

package main

import (
	"fmt"

	goioc "github.com/dgawlik/go-ioc"
)

type Operation func(x int) int

func double(x int) int {
	return x * 2
}

func quad(x int) int {
	return x * 4
}

func main() {

	goioc.BindInject[Operation](func(props goioc.Properties) func(x int) int {
		v, _ := props.String("mode")
		if v == "double" {
			return double
		} else {
			return quad
		}
	})

	goioc.SetProperty("mode", "double")

	op, _ := goioc.Resolve[Operation](true)

	fmt.Printf("Operation double: %d -> %d\n", 2, op(2))

	goioc.SetProperty("mode", "quad")

	op, _ = goioc.Resolve[Operation](true)

	fmt.Printf("Operation quad: %d -> %d\n", 2, op(2))

}

How to refresh dependencies

package main

import (
	"fmt"

	goioc "github.com/dgawlik/go-ioc"
)

type Fn func(elem int) bool

type Filter func(arr []int) []int

func main() {

	goioc.Bind[Fn](func(el int) bool {
		if el%2 == 0 {
			return true
		} else {
			return false
		}
	})

	goioc.BindInject[Filter](func(f Fn) func([]int) []int {

		return func(arr []int) []int {
			var newArr []int

			for _, e := range arr {
				if f(e) {
					newArr = append(newArr, e)
				}
			}

			return newArr
		}

	})

	arr := [10]int{1, 2, 3, 4, 5, 7, 8, 9}

	filter, _ := goioc.Resolve[Filter](true)

	fmt.Println(filter(arr[:]))

	goioc.Bind[Fn](func(el int) bool {
		if el%2 == 1 {
			return true
		} else {
			return false
		}
	})

	filter, _ = goioc.Resolve[Filter](true)

	fmt.Println(filter(arr[:]))

}