dimple

package module
v0.0.20 Latest Latest
Warning

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

Go to latest
Published: Mar 4, 2024 License: MIT Imports: 8 Imported by: 3

README

Dimple (DI)

Lightweight dependency injection container library for Golang inspired by Pimple.

Install

go get github.com/phramz/dimple

Usage

Here a very basic example how it works in general:

func main() {
	// Builder() returns an instance of ContainerBuilder to configure the container
	builder := dimple.Builder(
		// let's add our favorite time format as parameter to the container so other
		// services can pick it up
		dimple.Param("config.time_format", time.Kitchen),

		// we can just add an anonymous function as factory for our "logger" service since it does not
		// depend on other services
		// and therefore does not need any context
		dimple.Service("logger", dimple.WithFn(func() any {
			logger := logrus.New()
			logger.SetOutput(os.Stdout)

			return logger
		})),

		// this service depends on the "logger" to output the time and "config.time_format" for the
		// desired format.
		// that is why we need to use WithContextFn to get to the container and context
		dimple.Service("service.time", dimple.WithContextFn(func(ctx dimple.FactoryCtx) (any, error) {
			// you get can whatever dependency you need from the container as
			// long as you do not create a circular dependency
			logger := ctx.Container().MustGet("logger").(*logrus.Logger)
			format := ctx.Container().MustGet("config.time_format").(string)

			return &TimeService{
				logger: logger.WithField("service", ctx.ServiceID()),
				format: format,
			}, nil
		})),
	)

	// once we're done building we can retrieve a new instance of Container
	container, err := builder.Build(context.Background())
	if err != nil {
		panic(err)
	}

	// this is optional, but recommend since it will instantiate all service eager and
	// would return an error if there are issues. We don't want it to panic during runtime but
	// rather error on startup
	if err = container.Boot(); err != nil {
		panic(err)
	}

    // retrieve the *TimeService instance via generic function. Beware that illegal type assertions will panic
    timeService := dimple.MustGetT[*TimeService](container, "service.time")
    timeService.Now()
}

Full example see examples/basic/main.go

Tags

It is possible to annotate public struct members using the inject tag to get all necessary dependencies without explicitly registering a struct as service and the need of a factory function.


type TaggedTimeService struct {
	Logger *logrus.Logger `inject:"logger"`             // each tag point to the registered definition ID
	Format string         `inject:"config.time_format"` // fields must be public for this to work!
}

func (t *TaggedTimeService) Now() {
	t.Logger.Infof("It is %s", time.Now().Format(t.Format))
}

func main() {
	container := dimple.Builder(
		dimple.Param("config.time_format", time.Kitchen),
		dimple.Service("logger", dimple.WithFn(func() any {
			l := logrus.New()
			l.SetLevel(logrus.DebugLevel)
			return l
		})),
		dimple.Service("service.time", dimple.WithInstance(&TaggedTimeService{})),
	).
		MustBuild(context.Background())

    timeService := dimple.MustGetT[*TaggedTimeService](container, "service.time")
    logger := dimple.MustGetT[*logrus.Logger](container, "logger")

    logger.Debug("timeService says ...")
    timeService.Now()

    // if you want to inject services into struct which is not itself registered as a service
    // you might do it using Inject()
    anotherInstance := &TaggedTimeService{}
    if err := container.Inject(anotherInstance); err != nil {
        panic(err)
    }

    logger.Debug("anotherInstance says ...")
    anotherInstance.Now()
}

Full example see examples/basic/main.go

Decorators

Decorator can be used to wrap a service with another.


// TimeServiceInterface it might be beneficial to have an interface for abstraction when it comes to service decoration
type TimeServiceInterface interface {
	Now()
}

// OriginTimeService is the service we want to decorate
type OriginTimeService struct {
	Logger *logrus.Logger `inject:"logger"`
	Format string         `inject:"config.time_format"`
}

func (t *OriginTimeService) Now() {
	t.Logger.Infof("It is %s", time.Now().Format(t.Format))
}

// TimeServiceDecorator will decorate OriginTimeService
type TimeServiceDecorator struct {
	Logger    *logrus.Logger `inject:"logger"`
	Decorated TimeServiceInterface
}

func (t *TimeServiceDecorator) Now() {
	t.Logger.Infof("%d seconds elapsed since January 1, 1970 UTC", time.Now().Unix())
	t.Decorated.Now() // let's call the origin TaggedService as well
}

func main() {
	container := dimple.Builder(
		dimple.Param("config.time_format", time.Kitchen),
		dimple.Service("logger", dimple.WithInstance(logrus.New())),
		dimple.Service("service.time", dimple.WithInstance(&OriginTimeService{})),
		dimple.Decorator("service.time.decorator", "service.time", dimple.WithContextFn(func(ctx dimple.FactoryCtx) (any, error) {
			return &TimeServiceDecorator{
				// we can get the inner (origin) instance if we need to
				Decorated: ctx.Decorated().(TimeServiceInterface),
			}, nil
		})),
	).
		MustBuild(context.Background())

    // now when we call MustGet() we will actually receive the TimeServiceDecorator
	// instead of OriginTimeService
    timeService := dimple.MustGetT[TimeServiceInterface](container, "service.time")
    timeService.Now()
}

Full example see examples/decorator/main.go

Build-in services

Container

You can always get or inject the service container instance by addressing the service-id container e.g.:

type ContainerAwareService struct {
	container Container `inject:"container"`
}
Context

You can always get or inject the context given at Build() by addressing the service-id context e.g.:

type ContextAwareService struct {
	ctx context.Context `inject:"context"`
}

Credits

This library is based on various awesome open source libraries kudos going to:

License

This project is licensed under MIT License. See LICENSE file.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrCircularDependency is returned when a dependency cycle has been detected
	ErrCircularDependency = errors.New("circular dependency detected")
	// ErrUnknownService is returned if a requested service does not exist
	ErrUnknownService = errors.New("unknown service")
	// ErrServiceFactoryFailed is returned when the factory cannot instantiate the service
	ErrServiceFactoryFailed = errors.New("factory failed to instantiate service")
)

Functions

func MustGetT added in v0.0.10

func MustGetT[T any](c Container, id string) T

MustGetT generic wrapper for Container.MustGet

Types

type Container

type Container interface {
	// Has will return TRUE when a service or param by given id exist, otherwise FALSE
	Has(id string) bool

	// Get will return a plain value (for ParamDef) or the instance (for ServiceDef and DecoratorDef) by id
	Get(id string) (any, error)

	// MustGet will return the param value or service instance by id
	// Beware that this can panic at runtime if any instantiation errors occur!
	// Consider to explicitly call Boot() before using it
	MustGet(id string) any

	// Inject will take a struct as target and sets the field values according to tagged service ids.
	// Example:
	//
	// type MyStruct struct {
	//     TimeService     *TimeService   `inject:"service.time"`
	//     TimeFormat      string         `inject:"param.time_format"`
	// }
	Inject(target any) error

	// Boot will instantiate all services eagerly. It is not mandatory to call Boot() since all
	// services (except decorated services) will be instantiated lazy per default.
	// Beware that lazy instantiation can cause a panic at runtime when retrieving values via MustGet()!
	// If you want to ensure that every service can be instantiated properly it is recommended to call Boot()
	// before first use of MustGet().
	Boot() error

	// Ctx returns the context.Context
	Ctx() context.Context
}

Container abstraction interface

type ContainerBuilder added in v0.0.10

type ContainerBuilder interface {
	// Add will add a new Definition to the ContainerBuilder. The val argument can be either a
	// - ServiceDef for a service
	// - DecoratorDef if you want to decorate another service
	// - ParamDef any parameter value of any type
	Add(def Definition) ContainerBuilder

	// Get returns a Definition by its ID, otherwise nil if it does not exist
	Get(id string) Definition

	// Has returns TRUE if a Definition of given ID exists
	Has(id string) bool
}

ContainerBuilder abstraction interface

type DecoratorDef

type DecoratorDef interface {
	Definition
	Factory() Factory
	Instance() any
	Decorates() string
	Decorated() Definition
	WithID(id string) DecoratorDef
	WithFactory(factory Factory) DecoratorDef
	WithInstance(instance any) DecoratorDef
	WithDecorates(id string) DecoratorDef
	WithDecorated(def Definition) DecoratorDef
}

DecoratorDef abstraction interface

func Decorator

func Decorator(id string, decorates string, factory Factory) DecoratorDef

Decorator returns a new instance of DecoratorDef

type DefaultBuilder added in v0.0.10

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

func Builder added in v0.0.10

func Builder(defs ...Definition) *DefaultBuilder

Builder returns a new ContainerBuilder instance

func (*DefaultBuilder) Add added in v0.0.10

func (*DefaultBuilder) Build added in v0.0.10

func (*DefaultBuilder) Get added in v0.0.10

func (b *DefaultBuilder) Get(id string) Definition

func (*DefaultBuilder) Has added in v0.0.10

func (b *DefaultBuilder) Has(id string) bool

func (*DefaultBuilder) MustBuild added in v0.0.10

func (b *DefaultBuilder) MustBuild(ctx context.Context) *DefaultContainer

type DefaultContainer added in v0.0.10

type DefaultContainer struct {
	sync.Mutex
	// contains filtered or unexported fields
}

func (*DefaultContainer) Boot added in v0.0.10

func (c *DefaultContainer) Boot() error

func (*DefaultContainer) Ctx added in v0.0.10

func (c *DefaultContainer) Ctx() context.Context

func (*DefaultContainer) Get added in v0.0.10

func (c *DefaultContainer) Get(id string) (any, error)

func (*DefaultContainer) Has added in v0.0.10

func (c *DefaultContainer) Has(id string) bool

func (*DefaultContainer) Inject added in v0.0.10

func (c *DefaultContainer) Inject(target any) error

func (*DefaultContainer) MustGet added in v0.0.10

func (c *DefaultContainer) MustGet(id string) any

type Definition

type Definition interface {
	Id() string
}

Definition abstraction interface

type Factory added in v0.0.6

type Factory interface {
	FactoryFn() FactoryFn
	FactoryFnWithError() FactoryFnWithError
	FactoryFnWithContext() FactoryFnWithContext
	Instance() any
}

func WithContextFn added in v0.0.6

func WithContextFn(fn FactoryFnWithContext) Factory

func WithErrorFn added in v0.0.6

func WithErrorFn(fn FactoryFnWithError) Factory

func WithFn added in v0.0.6

func WithFn(fn FactoryFn) Factory

func WithInstance added in v0.0.7

func WithInstance(instance any) Factory

type FactoryCtx

type FactoryCtx interface {
	context.Context
	Ctx() context.Context
	Container() Container
	Decorated() any
	ServiceID() string
}

type FactoryFn

type FactoryFn = func() any

FactoryFn plain without anything

type FactoryFnWithContext added in v0.0.6

type FactoryFnWithContext = func(ctx FactoryCtx) (any, error)

FactoryFnWithContext to define an anonymous functions

type FactoryFnWithError added in v0.0.6

type FactoryFnWithError = func() (any, error)

FactoryFnWithError to define an anonymous functions

type ParamDef

type ParamDef interface {
	Definition
	Value() any
	WithID(id string) ParamDef
	WithValue(v any) ParamDef
}

ParamDef abstraction interface

func Param

func Param(id string, v any) ParamDef

Param returns a new instance of ParamDef

type ServiceDef

type ServiceDef interface {
	Definition
	Factory() Factory
	Instance() any
	WithID(id string) ServiceDef
	WithFactory(factory Factory) ServiceDef
	WithInstance(instance any) ServiceDef
}

ServiceDef abstraction interface

func Service

func Service(id string, factory Factory) ServiceDef

Service returns a new instance of ServiceDef

Directories

Path Synopsis
examples
basic Module
decorator Module
tags Module

Jump to

Keyboard shortcuts

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