axon

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: May 23, 2022 License: Apache-2.0 Imports: 9 Imported by: 26

README

axon

Go Report Card License godoc

A simple, lightweight, and lazy-loaded DI (really just a singleton management) library that supports generics. Influenced by multiple DI libraries but more specifically Google's Guice.

Install

go get github.com/eddieowens/axon

Usage

Basic

Simple add and get with a string key

package main

import (
  "fmt"
  "github.com/eddieowens/axon"
)

func main() {
  axon.Add("AnswerToTheUltimateQuestion", 42)
  answer := axon.MustGet(axon.WithKey("AnswerToTheUltimateQuestion"))
  fmt.Println(answer) // prints 42
}

You can also use a type as a key

package main

import (
  "fmt"
  "github.com/eddieowens/axon"
)

func main() {
  axon.Add(axon.NewTypeKey[int](42))
  answer := axon.MustGet[int]()
  fmt.Println(answer) // prints 42
}
Injecting dependencies

To inject dependencies to a struct, you can use the Inject func.

package main

import (
  "fmt"
  "github.com/eddieowens/axon"
)

type Struct struct {
  Answer int `inject:"AnswerToTheUltimateQuestion"`
}

func main() {
  axon.Add("AnswerToTheUltimateQuestion", 42)

  s := new(Struct)
  _ = axon.Inject(s)
  fmt.Println(s.Answer) // prints 42
}

A more full fledged example

package main

import (
  "fmt"
  "github.com/eddieowens/axon"
  "os"
)

type DatabaseClient interface {
  DeleteUser(user string) error
}

type databaseClient struct {
}

func (d *databaseClient) DeleteUser(_ string) error {
  fmt.Println("Deleting user!")
  return nil
}

type ServerConfig struct {
  Port int `inject:"port"`
}

type Server struct {
  // inject whatever is the default for the DBClient type
  DB           DatabaseClient `inject:",type"`
  ServerConfig ServerConfig   `inject:"config"`
}

func main() {
  axon.Add("port", os.Getenv("PORT"))

  // default implementation for DatabaseClient
  axon.Add(axon.NewTypeKey[DatabaseClient](new(databaseClient)))

  // construct the Config whenever it's needed (only ever called once)
  axon.Add("config", axon.NewFactory[ServerConfig](func(_ axon.Injector) (ServerConfig, error) {
    return ServerConfig{}, nil
  }))

  s := new(Server)
  _ = axon.Inject(s)
  fmt.Println(s.ServerConfig.Port)     // prints value of env var PORT
  fmt.Println(s.DB.DeleteUser("user")) // prints Deleting user!
}

For more examples and info, check out the GoDoc

Documentation

Overview

Package axon is a simple and generic-friendly DI library.

Example

A full-scale example of injecting values into a struct

package main

import (
	"fmt"
	"github.com/eddieowens/axon"
	"os"
)

// A full-scale example of injecting values into a struct
func main() {
	axon.Add(axon.NewKey("secret"), os.Getenv("LOGIN_SECRET"))
	axon.Add(axon.NewTypeKey[LoginServiceClient](new(loginServiceClient)))
	axon.Add(axon.NewTypeKey[UserService](new(userService)))
	axon.Add(axon.NewTypeKeyFactory[DBClient](axon.NewFactory[DBClient](func(_ axon.Injector) (DBClient, error) {
		// inject username, DB name, password, etc.
		return &dbClient{}, nil
	})))

	api := new(ApiGateway)
	_ = axon.Inject(api)
	api.UserService.DeleteUser("user")

}

type DBClient interface {
	DeleteUser(username string)
}

type dbClient struct {
}

func (d *dbClient) DeleteUser(username string) {
	fmt.Println("Deleting", username, "from DB!")
}

type LoginServiceClient interface {
	Logout(username string)
}

type loginServiceClient struct {
	// Injects the secret via a key called "secret"
	LoginSecret string `inject:"secret"`
}

func (l *loginServiceClient) Logout(username string) {
	fmt.Println("Logout for", username)
}

type UserService interface {
	DeleteUser(username string)
}

type userService struct {
	// Injects the default LoginServiceClient implementation.
	LoginServiceClient LoginServiceClient `inject:",type"`
	DBClient           DBClient           `inject:",type"`
}

func (u *userService) DeleteUser(username string) {
	u.DBClient.DeleteUser(username)
	u.LoginServiceClient.Logout(username)
	fmt.Println("Successfully deleted user")
}

type ApiGateway struct {
	UserService UserService `inject:",type"`
}
Output:

Deleting user from DB!
Logout for user
Successfully deleted user

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// InjectTag is the tag to look up within a struct for injection.
	InjectTag = "inject"

	// InjectTagValueType instructs the Injector to use the type as a Key rather than the name. If a name is specified,
	// the name takes precedence.
	InjectTagValueType = "type"
)
View Source
var (
	// ErrPtrToStruct if Inject is not passed a ptr to struct.
	ErrPtrToStruct = errors.New("value must be a ptr to a struct")

	// ErrNotFound key is not found in the Injector.
	ErrNotFound = errors.New("not found")

	// ErrInvalidType the type in the Injector is not the same as the type that is being injected.
	ErrInvalidType = errors.New("invalid type")

	// ErrInvalidField the field is not settable.
	ErrInvalidField = errors.New("invalid field")
)
View Source
var DefaultInjector = NewInjector()

DefaultInjector acts as a global-level Injector for all operations. If you want to create your own Injector, use NewInjector.

Functions

func Add added in v0.7.0

func Add[K InjectableKey](key K, val any, opts ...opts.Opt[AddOpts])

Add same as InjectAdd but uses the DefaultInjector.

Example

Simple example of adding values to the injector.

package main

import (
	"fmt"
	"github.com/eddieowens/axon"
)

func main() {
	// Use a string key
	axon.Add("key", "val")
	val := axon.MustGet[string](axon.WithKey("key"))
	fmt.Println(val)

	// Use a type as a key
	axon.Add(axon.NewTypeKey(1))
	i := axon.MustGet[int]()
	fmt.Println(i)

}
Output:

val
1

func Get added in v0.7.0

func Get[V any](opts ...opts.Opt[GetOpts]) (V, error)

Get same as InjectorGet but uses the DefaultInjector.

Example

Simple example of getting values from the injector.

package main

import (
	"fmt"
	"github.com/eddieowens/axon"
)

func main() {
	axon.Add("key", "val")
	val := axon.MustGet[string](axon.WithKey("key"))
	fmt.Println(val)

}
Output:

val

func Inject added in v0.7.0

func Inject[V any](val V, opt ...opts.Opt[InjectorInjectOpts]) error

Inject injects all fields in val marked with the InjectTag using the DefaultInjector. If any errors are encountered when trying to inject values to val, an error is returned. val must be a ptr to a struct.

Example

Simple example of injecting values into a struct

package main

import (
	"fmt"
	"github.com/eddieowens/axon"
)

func main() {
	type ExampleStruct struct {
		Val string `inject:"val"`
		Int int    `inject:",type"`
	}

	axon.Add("val", "val")
	axon.Add(axon.NewTypeKey[int](1))

	// Only struct pointers allowed.
	out := new(ExampleStruct)
	_ = axon.Inject(out)

	fmt.Println(out.Val)
	fmt.Println(out.Int)

}
Output:

val
1

func InjectAdd added in v0.7.0

func InjectAdd[K InjectableKey](inj Injector, key K, val any, _ ...opts.Opt[AddOpts])

InjectAdd adds a value into the inj using a key. If InjectAdd is called on a pre-existing value, it is overwritten.

func InjectorGet added in v0.7.0

func InjectorGet[V any](inj Injector, ops ...opts.Opt[GetOpts]) (out V, err error)

InjectorGet calls Injector.Get but also adds support for generics. This adds some convenience in type casting

Add(axon.NewTypeKey(1)) // anytime someone asks for the type "int" the value "1" is injected.
myInt, err := InjectorGet[int]()
fmt.Println(myInt) // prints "1"

func MustGet added in v0.7.0

func MustGet[V any](opts ...opts.Opt[GetOpts]) V

MustGet same as InjectorGet but panics if an error is encountered.

func NewTypeKeyFactory added in v0.7.0

func NewTypeKeyFactory[V any](val Factory) (Key, Factory)

NewTypeKeyFactory rather than tying a particular type to a value like NewTypeKey, this func ties a type to a Factory.

func NewTypeKeyProvider added in v0.7.0

func NewTypeKeyProvider[V any](val V) (Key, *Provider[V])

NewTypeKeyProvider rather than tying a particular type to a value like NewTypeKey, this func ties a type to a provider.

func WithKey added in v0.7.0

func WithKey[V InjectableKey](k V) opts.Opt[GetOpts]

WithKey specifies an InjectableKey to Get. If not specified, all Get funcs return the specified generic type.

Add("mykey", 1)
mykey := MustGet[int](WithKey("mykey"))
fmt.Println(mykey) // prints 1

func WithSkipFieldErrs added in v0.7.0

func WithSkipFieldErrs() opts.Opt[InjectorInjectOpts]

WithSkipFieldErrs allows for the Injector.Inject method to skip over field errors that are encountered when attempting to inject values onto a struct. The Injector.Inject method may still return an error, but they will not be due to problems encountered on individual fields.

Types

type AddOpts added in v0.7.0

type AddOpts struct {
}

type Factory

type Factory interface {
	Build(inj Injector) (any, error)
}

Factory produces the specified type whenever the Injector is retrieving the value (e.g. during Injector.Get or Injector.Inject). This factory will only ever be called once to construct the value unless a downstream dependency changes. Any Injector.Get method calls within the Build method will be registered as dependencies of the resulting type.

func NewFactory added in v0.7.0

func NewFactory[T any](f FactoryFunc[T]) Factory

NewFactory creates a Factory.

Example
type Service struct {
	DBClient DBClient `inject:",type"`
}

axon.Add(axon.NewTypeKeyFactory[DBClient](axon.NewFactory[DBClient](func(_ axon.Injector) (DBClient, error) {
	// construct the DB client.
	return &dbClient{}, nil
})))

s := new(Service)
_ = axon.Inject(s)
s.DBClient.DeleteUser("user")
Output:

Deleting user from DB!

type FactoryFunc added in v0.7.0

type FactoryFunc[T any] func(inj Injector) (T, error)

func (FactoryFunc[T]) Build added in v0.7.0

func (f FactoryFunc[T]) Build(inj Injector) (any, error)

type GetOpts added in v0.7.0

type GetOpts struct {
	// A specific Key to get from the Injector.
	Key Key
}

type InjectableKey added in v0.7.0

type InjectableKey interface {
	Key | string
}

InjectableKey is a type constraint for the supported keys within the Injector.

type Injector

type Injector interface {
	// Inject injects all fields on a struct that are tagged with the InjectTag from the Injector. d must be a pointer to
	// a struct and the fields that are tagged must be public. If the InjectTag is not present on the struct or if the
	// value is already set, it will not be injected.
	//
	// All errors should be checked with errors.Is as they may be wrapped.
	Inject(d any, opts ...opts.Opt[InjectorInjectOpts]) error

	// Add adds the val indexed by a Key. The underlying value for a Key should be a comparable value since the underlying
	// implementation utilizes a map. All calls to Add will overwrite existing values and no checks are done. Be aware that
	// if Add overwrites an existing value, all of that values dependencies will be reconstructed on the next call to Get or
	// Inject.
	//
	// If you want any updates made here to be reflected within the value themselves, use a provider.
	Add(key Key, val any, ops ...opts.Opt[InjectorAddOpts])

	// Get gets a value given a Key. If Get is unable to find the Key, ErrNotFound is returned. The first call to Get will
	// cause the underlying value to be constructed if it is a Factory.
	Get(k Key, o ...opts.Opt[InjectorGetOpts]) (any, error)
}

Injector allows for the storage, retrieval, and construction of objects.

func NewInjector

func NewInjector() Injector

NewInjector constructs a new Injector.

type InjectorAddOpts added in v0.7.0

type InjectorAddOpts struct {
}

InjectorAddOpts opts for the Injector.Add method.

type InjectorGetOpts added in v0.7.0

type InjectorGetOpts struct {
}

InjectorGetOpts opts for the Injector.Get method.

type InjectorInjectOpts added in v0.7.0

type InjectorInjectOpts struct {
	// See WithSkipFieldErrs.
	SkipFieldErr bool
}

InjectorInjectOpts opts for the Injector.Inject method.

type Key added in v0.7.0

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

Key the key type for the Injector.

func NewKey added in v0.7.0

func NewKey[V KeyConstraint](val V) Key

NewKey creates a general purpose Key with any KeyConstraint value. Generally used with a string

NewKey("my_key")

func NewTypeKey added in v0.7.0

func NewTypeKey[V any](val V) (Key, V)

NewTypeKey returns a Key using the type V as well as the passed in value val.

func (Key) IsEmpty added in v0.7.0

func (k Key) IsEmpty() bool

func (Key) String added in v0.7.0

func (k Key) String() string

type KeyConstraint added in v0.7.0

type KeyConstraint interface {
	string
}

type MutableValue added in v0.7.0

type MutableValue interface {

	// SetValue is called when the Injector is looking to inject a value to the implementor. val may not be the same expected
	// type or could also be nil. All implementations should do type checks as well as nil checks.
	//
	// Because this causes a mutation of the implementor, this method should always be implemented via a pointer receiver.
	SetValue(val any) error
}

MutableValue allows for any implementation to control how a value is being set within the Injector.

Normally the Injector sets an injected value via reflect.ValueOf(instance).Set(value) but if you want to have some instance managed by the Injector be mutated in a specific way, you can implement MutableValue with a pointer receiver.

type OnConstructFunc added in v0.7.0

type OnConstructFunc[T any] func(constructed container[T]) error

type Provider

type Provider[T any] struct {
	// contains filtered or unexported fields
}

Provider allows values to be mutated in real-time in a thread-safe manner. Providers should be used when you have a source of data which you want to be updated in multiple places at once even after leaving the Injector. Values returned by Provider.Get should never be stored as that defeats the purpose of the Provider altogether. Instead, Provider.Get should be called every time one wants to read the data which the Provider provides.

For example

Add("one", 1)
one := MustGet[int]("one")
fmt.Println(one) // prints 1
Add("one", 2)
fmt.Println(one) // still prints 1

To allow for dynamic value updates, you can use a provider.

Add("one", NewProvider(1))
one := MustGet[*Provider[int]]("one")
fmt.Println(one.Get()) // prints 1
Add("one", NewProvider(2))
fmt.Println(one.Get()) // prints 2

func NewProvider

func NewProvider[T any](val T) *Provider[T]
Example

Whenever Injector.Add is called, the value is updated within the Injector only. If you've already called Get from the Injector before an Add is called, the value you hold is not updated, for example

package main

import (
	"fmt"
	"github.com/eddieowens/axon"
)

func main() {
	type Server struct {
		ApiKey *axon.Provider[string] `inject:"api_key"`
	}

	axon.Add("api_key", axon.NewProvider("123"))

	server := new(Server)
	_ = axon.Inject(server)

	fmt.Println(server.ApiKey.Get())

	axon.Add("api_key", axon.NewProvider("456"))

	fmt.Println(server.ApiKey.Get())

}
Output:

123
456

func (*Provider[T]) Get added in v0.7.0

func (p *Provider[T]) Get() T

func (*Provider[T]) Set added in v0.7.0

func (p *Provider[T]) Set(val T)

func (*Provider[T]) SetValue added in v0.7.0

func (p *Provider[T]) SetValue(val any) error

SetValue for a Provider, this supports a val of either T or *Provider[T].

type Resolver added in v0.7.0

type Resolver interface {
	// contains filtered or unexported methods
}

type StorageGetter added in v0.7.0

type StorageGetter interface {
	Find(f maps.FindFunc[any, containerProvider[any]]) containerProvider[any]
	Get(k any) containerProvider[any]
}

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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