bindly

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

README

Bindaly - Golang Dependency Injection

GoReportCard GoDoc

Bindaly is a powerful, flexible dependency injection library for Go that helps manage application component dependencies with minimal boilerplate and maximal type safety.

Introduction

Bindaly provides a straightforward way to inject dependencies into Go structs using struct tags. It works with generic types to provide both flexibility and type safety, and supports caching, transformations, and complex dependency graphs.

Features

  • Type-safe dependency injection with generics
  • Annotation-based binding with struct tags
  • Value transformations via custom transformers
  • Intelligent type conversion between compatible types
  • Value caching for performance optimization
  • Fully concurrent-safe operations
  • Extensible architecture with custom providers and locators

Installation

go get github.com/viant/binder

Usage

Basic Example

package main

import (
	"context"
	"fmt"
	"github.com/viant/bindly"
	"github.com/viant/bindly/locator/buildin"
	"github.com/viant/structology"
)

type AppConfig struct {
	ServerPort int
	BaseURL    string
}

// Logger is a simple logging service
type Logger interface {
	Log(message string)
}

// SimpleLogger implements Logger
type SimpleLogger struct{}

func (l *SimpleLogger) Log(message string) {
	fmt.Println(message)
}

type DependencySetup struct {
	Config     *AppConfig
	Settings   map[string]interface{}
	Interfaces map[string]interface{}
}

// Service uses the configuration
type Service struct {
	Debug      bool   `bind:"kind=setting,in=debug"`
	ServerPort int    `bind:"in=Config.ServerPort"` //state kind is a default kind
	Logger     Logger //interface bind by default to interface kind
}

func main() {

	var iLogger Logger
	appLoger := &SimpleLogger{}

	setup := &DependencySetup{
		Config: &AppConfig{
			ServerPort: 8080,
			BaseURL:    "http://localhost:8080",
		},

		Interfaces: map[string]interface{}{
			structology.InterfaceTypeOf(&iLogger).String(): appLoger,
		},
		Settings: map[string]interface{}{
			"debug": true,
			"port":  8080,
		},
	}

	var opts = append([]bindly.InjectorOption{}, bindly.WithProviders(
		buildin.Struct("state", "", 1),
		buildin.Map("setting", "Settings", 1),
		buildin.Map("interface", "Interfaces", 1)))

	injector := bindly.NewInjector(opts...)
	service := &Service{}
	err := bindly.WithState[Service](injector, setup).Inject(context.Background(), service)
	fmt.Println(service, err)
}

Binding with Tags

Bindaly uses struct tags to define dependencies:

type MyStruct struct {
    // Inject from state with key "config.port"
    Port int `bind:"in=config.port"`
    
    // Inject from a specific provider kind
    Database *Database `bind:"kind=database,in=primary"`
    
    // Cache the resolved value
    ExpensiveData []Item `bind:"kind=service,in=data,cacheable"`
    
    // Transform values during injection
    ConfigValue string `bind:"in=rawValue" xform:"string"`
}

Value Transformers

Transformers convert values during injection:

// Register custom transformers
transformerRegistry := xform.NewRegistry()
xform.conv.Init(transformerRegistry)  // Initialize standard converters
transformerRegistry.Register("custom", xform.NewTransformerFactory("custom", NewCustomTransformer))

injector := bindly.New(
    bindly.WithTransformers(transformerRegistry),
)

Advanced Features

Caching Values

// Create a value cache
cache := bindly.NewValueCache()

// Use cache with binding context
bindingCtx := bindly.WithState[MyService](
    injector, 
    state,
    bindly.WithCache[MyService](cache),
)

// Save cache to disk
err := cache.Save(ctx, "/path/to/cache.bin")

// Load cache from disk
err := cache.Load(ctx, "/path/to/cache.bin")

Custom Providers

// Implement custom provider
type MyProvider struct {
    // provider implementation
}

func (p *MyProvider) Locate(state *structology.State) locator.Locator {
    // Return appropriate locator based on state
}

// Register provider
injector := bindly.New(
    bindly.WithProviders(&MyProvider{}),
)

Performance Considerations

  • Use cacheable for expensive operations
  • Prefer direct binding over transformations when possible
  • Consider pre-building binding types for frequently used structs

Thread Safety

The binder library is designed to be fully thread-safe. All caching operations use appropriate locking mechanisms to ensure safe concurrent access.

License

The source code is made available under the terms of the Apache License, Version 2.0. See the LICENSE file for more details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Documentation

Overview

Example (Inject)
package main

import (
	"context"
	"fmt"
	"github.com/viant/bindly"
	"github.com/viant/bindly/locator/buildin"
	"github.com/viant/structology"
)

type AppConfig struct {
	ServerPort int
	BaseURL    string
}

// Logger is a simple logging service
type Logger interface {
	Log(message string)
}

// SimpleLogger implements Logger
type SimpleLogger struct{}

func (l *SimpleLogger) Log(message string) {
	fmt.Println(message)
}

type DependencySetup struct {
	Config     *AppConfig
	Settings   map[string]interface{}
	Interfaces map[string]interface{}
}

// Service uses the configuration
type Service struct {
	Debug      bool   `bind:"kind=setting,in=debug"`
	ServerPort int    `bind:"in=Config.ServerPort"` //state kind is a default kind
	Logger     Logger //interface bind by defaul to interface kind
}

func main() {

	var iLogger Logger
	appLoger := &SimpleLogger{}

	dependencies := &DependencySetup{
		Config: &AppConfig{
			ServerPort: 8080,
			BaseURL:    "http://localhost:8080",
		},

		Interfaces: map[string]interface{}{
			structology.InterfaceTypeOf(&iLogger).String(): appLoger,
		},
		Settings: map[string]interface{}{
			"debug": true,
			"port":  8080,
		},
	}

	var opts = append([]bindly.InjectorOption{}, bindly.WithProviders(
		buildin.Struct("state", "", 1), //empty kind
		buildin.Map("setting", "Settings", 1),
		buildin.Map("interface", "Interfaces", 1)))

	injector, err := bindly.NewInjector(opts...)
	if err != nil {
		panic(err)
	}
	service := &Service{}
	err = bindly.WithState[Service](injector, dependencies).Inject(context.Background(), service)

	fmt.Println(service, err)
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BindOption added in v0.2.0

type BindOption func(*bindOptions)

func WithBindingObserver added in v0.2.0

func WithBindingObserver(observer BindingObserver) BindOption

func WithPlan added in v0.2.0

func WithPlan(plan *Plan) BindOption

func WithReplay added in v0.2.0

func WithReplay(binding ReplayBinding) BindOption

func WithSource added in v0.2.0

func WithSource(source any) BindOption

type Binding

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

Binding represents a binding

type BindingCache

type BindingCache struct {
	internal.Map[reflect.Type, *BindingType]
}

func NewBindingCache

func NewBindingCache() *BindingCache

type BindingContext

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

BindingContext represents binding context with state

func WithState

func WithState[T any](binder *Injector, state interface{}, opt ...BindingOption[T]) *BindingContext[T]

func (*BindingContext[T]) Inject

func (c *BindingContext[T]) Inject(ctx context.Context, target *T) error

Inject uses the same immutable-plan invocation path as Injector.Bind.

func (*BindingContext[T]) Value

func (c *BindingContext[T]) Value(ctx context.Context, location *state.Location) (any, bool, error)

type BindingError added in v0.2.0

type BindingError struct {
	Path    string
	Code    int
	Message string
	Cause   error
}

func (*BindingError) Error added in v0.2.0

func (e *BindingError) Error() string

func (*BindingError) StatusCode added in v0.2.0

func (e *BindingError) StatusCode() int

func (*BindingError) Unwrap added in v0.2.0

func (e *BindingError) Unwrap() error

type BindingEvent added in v0.2.0

type BindingEvent struct {
	Target   any
	Path     string
	Location state.Location
	Value    any
	Metadata any
}

BindingEvent describes one successfully assigned target field. Target is the actual Bind/BindTarget destination, not necessarily the initial input. Path is that target's full planned field selector. Value is the assigned value; Metadata is nil when its provenance is unknown or an opaque conversion ran. Later bindings served by the explicit persistent value cache also have nil metadata: opaque evidence is never persisted or reconstructed from a value. No values or metadata are logged automatically.

type BindingObserver added in v0.2.0

type BindingObserver func(context.Context, BindingEvent) error

BindingObserver runs after field and presence-marker assignment. An error aborts binding through BindingError; already applied assignments are not rolled back. Observers own any longer-lived recording or publication policy.

type BindingOption

type BindingOption[T any] func(ctx *BindingContext[T])

func WithCache

func WithCache[T any](cache *ValueCache) BindingOption[T]

type BindingSpec added in v0.2.0

type BindingSpec struct {
	MarkerField                                                              string
	Path, Name                                                               string
	Location                                                                 state.Location
	SourceType                                                               reflect.Type
	Required, Cacheable                                                      *bool
	MinAllowedRecords, MaxAllowedRecords, ExpectedReturned                   *int
	When, Scope, With, URI, ResourceRef, DataType, ErrorMessage, Cardinality string
	Async                                                                    bool
	DefaultValue, Extension                                                  any
	ErrorCode                                                                int
	Transformer                                                              xform.Transformer
}

func BindingSpecFromField added in v0.2.0

func BindingSpecFromField(field reflect.StructField) (BindingSpec, bool, error)

type BindingType

type BindingType struct {
	Bindings []Bindings
	Type     *structology.StateType
}

type Bindings

type Bindings []*Binding

func (Bindings) GroupByPriority

func (b Bindings) GroupByPriority(registry *locator.Registry) ([]Bindings, error)

type DynamicContext added in v0.2.0

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

DynamicContext binds runtime-owned targets through the canonical invocation.

func WithDynamicState added in v0.2.0

func WithDynamicState(injector *Injector, stateValue interface{}) *DynamicContext

func (*DynamicContext) Assign added in v0.2.0

func (c *DynamicContext) Assign(ctx context.Context, target interface{}, location *state.Location) error

Assign resolves a typed location and writes its value into a nonnil pointer.

func (*DynamicContext) Inject added in v0.2.0

func (c *DynamicContext) Inject(ctx context.Context, target interface{}) error

func (*DynamicContext) Value added in v0.2.0

func (c *DynamicContext) Value(ctx context.Context, location *state.Location) (interface{}, bool, error)

type Injector

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

Injector represents dependency injector

func NewInjector

func NewInjector(options ...InjectorOption) (*Injector, error)

NewInjector creates injector

func (*Injector) Bind added in v0.2.0

func (i *Injector) Bind(ctx context.Context, target any, options ...BindOption) error

func (*Injector) CompilePlan added in v0.2.0

func (i *Injector) CompilePlan(target reflect.Type, specs ...BindingSpec) (*Plan, error)

func (*Injector) ForScope added in v0.2.0

func (i *Injector) ForScope(providers ...locator.Provider) (*Injector, error)

func (*Injector) ReadResource added in v0.2.0

func (i *Injector) ReadResource(reference string) ([]byte, error)

func (*Injector) ResourceFS added in v0.2.0

func (i *Injector) ResourceFS(name string) (fs.FS, bool)

func (*Injector) Resources added in v0.2.0

func (i *Injector) Resources() *resource.Store

func (*Injector) TransformerRegistry

func (b *Injector) TransformerRegistry() *xform.Registry

TransformerRegistry returns the transformer registry

type InjectorOption

type InjectorOption func(*Injector)

func WithBindingTag

func WithBindingTag(tag string) InjectorOption

func WithLocators

func WithLocators(registry *locator.Registry) InjectorOption

func WithProviders

func WithProviders(providers ...locator.Provider) InjectorOption

func WithResourceFS added in v0.2.0

func WithResourceFS(name string, source fs.FS) InjectorOption

func WithResources added in v0.2.0

func WithResources(store *resource.Store) InjectorOption

func WithTransformerTag

func WithTransformerTag(tag string) InjectorOption

type Plan added in v0.2.0

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

Plan contains only immutable target metadata; providers are resolved per bind.

func (*Plan) Presence added in v0.2.0

func (p *Plan) Presence(target any, path string) (bool, error)

Presence reads a binding's compiled presence marker. Inputs without a marker carry values unconditionally. Missing marker pointers represent absence.

func (*Plan) Projection added in v0.2.0

func (p *Plan) Projection(fields ...ProjectionField) (*Projection, error)

func (*Plan) Replay added in v0.2.0

func (p *Plan) Replay(paths ...string) (*ReplayPlan, error)

func (*Plan) SetPresence added in v0.2.0

func (p *Plan) SetPresence(target any, path string, present bool) error

SetPresence restores presence through the same compiled marker used by Bind.

func (*Plan) TargetType added in v0.2.0

func (p *Plan) TargetType() reflect.Type

type Projection added in v0.2.0

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

func (*Projection) Resolver added in v0.2.0

func (p *Projection) Resolver(target any) ValueResolver

func (*Projection) TargetType added in v0.2.0

func (p *Projection) TargetType() reflect.Type

func (*Projection) Value added in v0.2.0

func (p *Projection) Value(target any, name string) (any, bool, error)

func (*Projection) Without added in v0.2.0

func (p *Projection) Without(target any, names ...string) (any, error)

type ProjectionField added in v0.2.0

type ProjectionField struct {
	Path  string
	Names []string
}

type Replay added in v0.2.0

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

Replay holds one detached set of source values. Prepared values can only be produced by successful native binding. It is invocation-owned, not shareable.

func (*Replay) MarshalJSON added in v0.2.0

func (r *Replay) MarshalJSON() ([]byte, error)

func (*Replay) Plan added in v0.2.0

func (r *Replay) Plan() *Plan

func (*Replay) Providers added in v0.2.0

func (r *Replay) Providers() ([]locator.Provider, error)

Providers exposes each selected source kind independently to dependency components. Missing selected inputs are authoritative absence, not fallback.

type ReplayBinding added in v0.2.0

type ReplayBinding struct {
	Replay *Replay
	Only   bool
	Gate   func(context.Context, any) error
}

type ReplayPlan added in v0.2.0

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

ReplayPlan is an immutable selection of external fields from one canonical binding plan. It retains no invocation values or dependency-read evidence.

func (*ReplayPlan) Capture added in v0.2.0

func (p *ReplayPlan) Capture(input any) (*Replay, error)

func (*ReplayPlan) CaptureSources added in v0.2.0

func (p *ReplayPlan) CaptureSources(ctx context.Context, providers []locator.Provider) (*Replay, error)

CaptureSources snapshots only this plan's selected source bindings. Providers are invocation-scoped; no target is initialized, transformer applied, default materialized or prerequisite/dependency evaluated here. Raw-source hooks retain JSON presence and exact numbers before the normal ReplayBinding preflight.

func (*ReplayPlan) DecodeJSON added in v0.2.0

func (p *ReplayPlan) DecodeJSON(data []byte) (*Replay, error)

func (*ReplayPlan) Prepare added in v0.2.0

func (p *ReplayPlan) Prepare(projection *Projection, paths ...string) (*ReplayPlan, error)

Prepare includes fresh, non-persisted bindings needed to authorize external input. Param dependencies are resolved through the plan's canonical aliases.

type StructTypeCache

type StructTypeCache struct {
	internal.Map[reflect.Type, *structology.StateType]
}

func NewStructTypeCache

func NewStructTypeCache() *StructTypeCache

type ValueCache

type ValueCache struct {
	internal.Map[string, interface{}]
	// contains filtered or unexported fields
}

func NewValueCache

func NewValueCache() *ValueCache

func (*ValueCache) Clear

func (c *ValueCache) Clear()

Clear empties the cache

func (*ValueCache) Load

func (c *ValueCache) Load(ctx context.Context, URL string) error

Load loads the cache from disk

func (*ValueCache) Save

func (c *ValueCache) Save(ctx context.Context, destURL string) error

Save persists the cache to disk

type ValueResolver added in v0.2.0

type ValueResolver func(string) (any, bool, error)

Directories

Path Synopsis
field
Package field applies immutable field indexes compiled by x/shape.
Package field applies immutable field indexes compiled by x/shape.
provider
body
Package body resolves whole or named request bodies to planned Go types.
Package body resolves whole or named request bodies to planned Go types.
request
Package request composes independently scoped HTTP value providers while preserving the original request pointer for handler capabilities.
Package request composes independently scoped HTTP value providers while preserving the original request pointer for handler capabilities.
values
Package values supplies an invocation-scoped source of authored values.
Package values supplies an invocation-scoped source of authored values.
Package resource owns named package filesystems shared by compilation and invocation.
Package resource owns named package filesystems shared by compilation and invocation.

Jump to

Keyboard shortcuts

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