autowire

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2024 License: MIT Imports: 5 Imported by: 0

README

Go Version GoDoc Build Status Coverage Status GoReport

Dependency injection for Go 1.18+ using Generics and reflection

Functionalities

  • Automatically wiring/injecting objects on creation.
  • Easy to use (see Usage section).
  • Shared mode by default that creates only one instance for a type (this option is configurable).
  • Ability to overwrite objects of specific types on building which is convenient for unit testing.
  • No code generation.

Installation

go get github.com/tiendc/autowire

Usage

General usage
    // Suppose you have ServiceA and a creator function
    type ServiceA interface {}
    func NewServiceA(srvB ServiceB, repoX RepoX) ServiceA {
        return <ServiceA-instance>
    }

    // and ServiceB
    type ServiceB interface {}
    func NewServiceB(ctx context.Context, repoX RepoX, repoY RepoY) (ServiceB, error) {
        return <ServiceB-instance>, nil
    }

    // and RepoX
    type RepoX interface {}
    func NewRepoX(s3Client S3Client) (RepoX, error) {
        return <RepoX-instance>, nil
    }

    // and RepoY
    type RepoY interface {}
    func NewRepoY(redisClient RedisClient) RepoY {
        return <RepoY-instance>
    }

    // and a struct provider
    type ProviderStruct struct {
        RedisClient RedisClient
        S3Client    S3Client
    }

    var (
        // init the struct value
        providerStruct = &ProviderStruct{...}

        // create a container with passing providers
        container = MustNewContainer([]any{
            // Service providers
            NewServiceA,
            NewServiceB,
            // Repo providers
            NewRepoX,
            NewRepoY,
            // Struct provider (must be a pointer)
            providerStruct,
        })
    )

    func main() {
        // Update some values of the struct provider
        providerStruct.RedisClient = newRedisClient()
        providerStruct.S3Client = newS3Client()

        // Create ServiceA
        serviceA, err := autowire.BuildWithCtx[ServiceA](ctx, container)
        // Create RepoX
        repoX, err := autowire.Build[RepoX](container)
    }
Non-shared mode
    // Set sharedMode when create a container
    container = MustNewContainer([]any{
        // your providers
    }, SetSharedMode(false))

    // Activate non-shared mode inline for a specific build only
    serviceA, err := Build[ServiceA](container, NonSharedMode())
Overwrite values of specific types

This is convenient in unit testing to overwrite specific types only.

    // In unit testing, you may want to overwrite some `repos` and `clients` with fake instances.
    // NOTE: you may need to use `non-shared mode` to make sure cached objects are not used.
    serviceA, err := Build[ServiceA](container, NonSharedMode(),
            ProviderOverwrite[RepoX](fakeRepoX),
            ProviderOverwrite[RepoY](fakeRepoY))
    serviceA, err := Build[ServiceA](container, NonSharedMode(),
            ProviderOverwrite[RedisClient](fakeRedisClient),
            ProviderOverwrite[S3Client](fakeS3Client))
Reclaim memory after use

Typically, dependency injection is only used at the initialization phase of a program. However, it can take some space in memory which will become wasted after the phase. Use the below trick to reclaim the memory taken by the autowire variables.

    var (
        // create a global container
        container = MustNewContainer(...)
    )

    func autowireCleanUp() {
        // Assign nil to allow the garbage collector to reclaim the taken memory
        container = nil
    }

    func main() {
        // Initialization phase
        // Create services and use them
        serviceA, _ := autowire.Build[ServiceA](container)
        serviceB, _ := autowire.Build[ServiceB](container)

        // Clean up the usage, make sure you won't use the vars any more
        autowireCleanUp()
    }

Contributing

  • You are welcome to make pull requests for new functions and bug fixes.

License

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrTypeCast           = errors.New("ErrTypeCast")
	ErrNotFound           = errors.New("ErrNotFound")
	ErrProviderInvalid    = errors.New("ErrProviderInvalid")
	ErrProviderDuplicated = errors.New("ErrProviderDuplicated")
	ErrCircularDependency = errors.New("ErrCircularDependency")
)

Functions

func Build

func Build[T any](c Container, opts ...ContextOption) (value T, err error)

Build builds object for the specified type within a container

func BuildWithCtx

func BuildWithCtx[T any](ctx context.Context, c Container, opts ...ContextOption) (value T, err error)

BuildWithCtx builds object for the specified type within a container. This function will pass the specified context object to every provider that requires a context.

func Get

func Get[T any](c Container) (value T, err error)

Get gets object of a type within a container. If no object is created for the type or `sharedMode` is `false`, ErrNotFound is returned.

Types

type Container

type Container interface {
	// SharedMode gets shared mode in the container (default is `true`).
	//
	// When `shared mode` is `true`, every object created within the container will be cached and returned
	// on future uses. Therefore, there is only an object created for a specific type.
	// For example: ServiceA requires ServiceX and ServiceY. ServiceB requires ServiceX and ServiceZ.
	// In this mode, ServiceX will be created only one time, so ServiceA and ServiceB will share the
	// same ServiceX object.
	SharedMode() bool

	// ProviderSet gets provider set within the container
	ProviderSet() ProviderSet

	// Get gets a value stored in the container for the specified type.
	// If not found, returns ErrNotFound.
	Get(targetType reflect.Type) (reflect.Value, error)

	// Build creates a value for the specified type and all other required values.
	Build(targetType reflect.Type, opts ...ContextOption) (reflect.Value, error)

	// BuildWithCtx creates a value for the specified type with passing a context.Context object.
	// The context object will be passed to every provider which requires a context.
	BuildWithCtx(ctx context.Context, targetType reflect.Type, opts ...ContextOption) (reflect.Value, error)

	// Resolve builds dependency graph for the specified type
	Resolve(targetType reflect.Type) (DependencyGraph, error)
	// contains filtered or unexported methods
}

Container is a storage for storing every object created by the providers of the container. Each container has its own provider set and configuration settings.

func MustNewContainer

func MustNewContainer(providers []any, opts ...ContainerConfigOption) Container

MustNewContainer creates a new container and panics on error.

func NewContainer

func NewContainer(providers []any, opts ...ContainerConfigOption) (Container, error)

NewContainer creates a new container with providing providers and settings. Provider list can contain:

  • functions in the below forms: func(<arg1>, ..., <argN>) <ServiceType> func(<arg1>, ..., <argN>) (<ServiceType>, error) func(context.Context, <arg1>, ..., <argN>) (<ServiceType>, error)
  • struct pointers
  • objects of type `ProviderSet`
  • objects of type `Provider`

type ContainerConfigOption

type ContainerConfigOption func(Container)

ContainerConfigOption config option setter used when create a container

func SetSharedMode

func SetSharedMode(flag bool) ContainerConfigOption

SetSharedMode config option for setting `sharedMode` for a container

type Context

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

Context a context object used in each building/resolving object

type ContextOption

type ContextOption func(*Context)

ContextOption configuration setter for a context

func NonSharedMode

func NonSharedMode() ContextOption

NonSharedMode sets `shared mode` to `false` for the current context

func ProviderOverwrite

func ProviderOverwrite[T any](val T) ContextOption

ProviderOverwrite overwrites a value for the current context

type DependencyGraph

type DependencyGraph struct {
	TargetType   reflect.Type
	Dependencies []DependencyGraph
}

DependencyGraph dependency graph info of a type.

func Resolve

func Resolve[T any](c Container) (DependencyGraph, error)

Resolve builds dependency graph for the specified type within a container

type Provider

type Provider interface {
	// Source returns provider source which can be a function, a struct pointer, or a value
	Source() any

	// TargetTypes returns a list of target types that the provider can create objects of.
	// A function provider can provide only one target type, whereas a struct provider can
	// provide multiple target types through its fields.
	TargetTypes() []reflect.Type

	// DependentTypes returns a list of types that will be required when the provider creates
	// objects of the target types. Typically, a function provider can have no dependent types
	// or multiple ones, whereas a struct provider have no dependent types.
	DependentTypes() []reflect.Type

	// Build builds an object of the specified type.
	Build(*Context, reflect.Type) (reflect.Value, error)
}

Provider a provider is an `object creator` and provide the object to a container. An object creation may require dependencies such as other objects or data. A `provider` should be able to resolve all dependencies and collect the required objects to create the expected object.

type ProviderSet

type ProviderSet interface {
	// GetFor returns a provider for the specified type, or ErrNotFound.
	GetFor(reflect.Type) (Provider, error)

	// GetAll returns all providers contained within the set
	GetAll() []Provider

	// Overwrite replaces the existing provider by its target type with the specified one
	Overwrite(Provider)
	// contains filtered or unexported methods
}

ProviderSet is a set of unique providers for certain unique types

func MustNewProviderSet

func MustNewProviderSet(args ...any) ProviderSet

MustNewProviderSet creates a ProviderSet with panicking on error

func NewProviderSet

func NewProviderSet(args ...any) (ProviderSet, error)

NewProviderSet creates a new provider set from individual providers. A provider object can be:

  • function in the below forms: func(<arg1>, ..., <argN>) <ServiceType> func(<arg1>, ..., <argN>) (<ServiceType>, error) func(context.Context, <arg1>, ..., <argN>) (<ServiceType>, error)
  • a struct pointer
  • an object of type `ProviderSet`
  • an object of type `Provider`

Jump to

Keyboard shortcuts

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