README
¶
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 ¶
- Variables
- func Build[T any](c Container, opts ...ContextOption) (value T, err error)
- func BuildWithCtx[T any](ctx context.Context, c Container, opts ...ContextOption) (value T, err error)
- func Get[T any](c Container) (value T, err error)
- type Container
- type ContainerConfigOption
- type Context
- type ContextOption
- type DependencyGraph
- type Provider
- type ProviderSet
Constants ¶
This section is empty.
Variables ¶
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.
Types ¶
type Container ¶
type Container interface { // // 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.
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`