English | δΈζ
go-kid/ioc is a Go runtime dependency injection (IoC/DI) framework based on tag + interface.
- Component Dependency Injection (
wiretag)- Inject by pointer type, interface type, or component name
- Multiple implementation selection strategies (Primary/Qualifier)
- Inject into interface slices/arrays
- Configuration Injection (
value/prop/prefixtags)- Multiple configuration sources (command-line args, files, raw content)
- Configuration placeholders
${...} - Expression evaluation
#{...}(arithmetic, logical, conditional, collection operations)
- Constructor Injection: Function-based dependency injection (built-in)
- Lifecycle Management: ApplicationRunner, CloserComponent, LazyInitComponent
context.Contextlifecycle support: WithContext variants for all lifecycle interfaces- Scope mechanism: Singleton/Prototype
- Conditional component registration: Register components based on runtime conditions
- Application event mechanism: Publish and listen for application events
- Type-safe generic registration:
ioc.Provide[T]validates return type at registration time log/slogadapter support: Integrate with Go's structured logging
Requires Go 1.21+.
go get github.com/go-kid/iocpackage main
import (
"fmt"
"github.com/go-kid/ioc"
)
// Component to be injected
type ComponentA struct {
Name string
}
func (a *ComponentA) GetName() string {
return a.Name
}
// Target struct that depends on ComponentA
type App struct {
ComponentA *ComponentA `wire:""` // pointer + exported field + wire tag
}
func main() {
a := new(App)
// Register components
ioc.Register(a)
ioc.Register(&ComponentA{Name: "Comp-A"})
// Run the framework
_, err := ioc.Run()
if err != nil {
panic(err)
}
// Output: "Comp-A"
fmt.Println(a.ComponentA.GetName())
}Key points: Dependency fields must be exported (start with uppercase) and use
wire:""to declare injection.
Component injection is controlled via tag: wire:"<name>,arg1=xxx,arg2=yyy"
<name>: optional, inject by component name,arg=...: optional extra arguments (e.g.,qualifier)wire:""(empty) means inject by type
type Component struct {
Name string
}
type App struct {
C *Component `wire:""` // injected by *Component type
}type IComponent interface {
GetName() string
}
type ComponentA struct{ Name string }
func (a *ComponentA) GetName() string { return a.Name }
type App struct {
C IComponent `wire:""` // inject by interface type
}Implement NamingComponent interface to define component names:
type ComponentA struct {
Name string
componentName string
}
func (a *ComponentA) Naming() string { return a.componentName }
type App struct {
ByName IComponent `wire:"comp-A"` // inject by component name
}When an interface has multiple implementations, the container selects by priority:
- Components implementing
WirePrimaryinterface (primary) - Components without alias (not implementing
Naming()) - One will be chosen (order not guaranteed)
Using Primary marker:
type ComponentA struct {
Name string
definition.WirePrimaryComponent // embed Primary component
}Using Qualifier:
type ComponentA struct {
qualifier string
}
func (a *ComponentA) Qualifier() string { return a.qualifier }
type App struct {
C IComponent `wire:",qualifier=comp-A"` // use qualifier
}type App struct {
All []Interface `wire:""` // all Interface implementations
}type configA struct {
B int `yaml:"b"`
C []int `yaml:"c"`
}
type App struct {
A *configA `prefix:"a"` // bind config prefix "a"
}Configuration (YAML):
a:
b: 123
c: [1,2,3,4]type T struct {
DSN string `value:"${db.dsn}"` // config placeholder
Sum int `value:"#{1+2}"` // expression
Str string `value:"foo"` // literal
}type DBConfig struct {
DSN string `prop:"db.dsn"` // equivalent to value:"${db.dsn}"
}${...}: Configuration placeholder, reads value from config#{...}: Expression evaluation, supports arithmetic, logical, conditional, collection operations
Example:
type T struct {
Arithmetic int `value:"#{1+(1*2)}"` // 3
Comparison bool `value:"#{1/1==1}"` // true
Conditional string `value:"#{1>2?'a':'b'}"` // "b"
WithConfig int `value:"#{${number.val1}+${number.val2}}"` // use config
}Constructor injection is built into the framework. Simply register the constructor directly:
func NewService(repo *Repository) *Service {
return &Service{repo: repo}
}
ioc.Register(NewService) // register constructor directly
ioc.Register(&Repository{})
_, err := ioc.Run()Type-safe variant with ioc.Provide[T]:
ioc.Provide[Service](NewService) // validates return type at registration timeStyle 1: Use ioc.Run / ioc.Register
ioc.Register(a)
ioc.Register(&ComponentA{Name: "Comp-A"})
_, err := ioc.Run()Style 2: Work with app.App Directly
application := app.NewApp()
err := application.Run(
app.SetComponents(...),
app.SetConfigLoader(loader.NewFileLoader("config.yaml")),
app.SetConfigBinder(binder.NewViperBinder("yaml")),
)ApplicationRunner: executed after all components are refreshedCloserComponent:Close()called when app stopsLazyInitComponent: marked as lazy initializationPriorityComponent: controls post-processor execution orderInitializeComponentWithContext/InitializingComponentWithContext: context-aware Init/AfterPropertiesSetApplicationRunnerWithContext: context-aware RunCloserComponentWithContext: context-aware CloseScopeComponent: control component scope (singleton/prototype)ConditionalComponent: conditional registration based on runtime conditionsApplicationEventListener/ApplicationEventPublisher: event mechanism
All lifecycle interfaces have WithContext variants. Components can implement them to receive a context.Context for timeout control, cancellation propagation, etc.
// Run the framework with a context
app, err := ioc.RunWithContext(ctx)
// Set a shutdown timeout
_, err := ioc.Run(app.SetShutdownTimeout(30 * time.Second))Context-aware lifecycle interfaces:
// InitializeComponentWithContext (replaces InitializeComponent)
func (c *MyComp) Init(ctx context.Context) error { return nil }
// InitializingComponentWithContext (replaces InitializingComponent)
func (c *MyComp) AfterPropertiesSet(ctx context.Context) error { return nil }
// ApplicationRunnerWithContext (can coexist with ApplicationRunner)
func (r *MyRunner) RunWithContext(ctx context.Context) error { return nil }
// CloserComponentWithContext (can coexist with CloserComponent)
func (c *MyComp) CloseWithContext(ctx context.Context) error { return nil }Note:
Init(ctx)andAfterPropertiesSet(ctx)have the same method names as their base interfaces but different signatures, so a component implements one or the other.RunWithContextandCloseWithContextuse distinct method names, allowing a component to implement both the original and context-aware versions.
Control component scope by implementing ScopeComponent:
- Singleton (default): single instance in the container
- Prototype: new instance created on every access
import "github.com/go-kid/ioc/definition"
type MyPrototype struct{}
func (p *MyPrototype) Scope() string { return definition.ScopePrototype }Implement ConditionalComponent to decide at runtime whether a component should be created:
import "github.com/go-kid/ioc/definition"
type MyComp struct{}
func (c *MyComp) Condition(ctx definition.ConditionContext) bool {
return ctx.HasComponent("dependency")
}Publish and listen for application events to enable loose coupling between components:
import "github.com/go-kid/ioc/definition"
// Listen for events
type MyListener struct{}
func (l *MyListener) OnEvent(event definition.ApplicationEvent) error {
// handle event
return nil
}Built-in events: ComponentCreatedEvent, ApplicationStartedEvent, ApplicationClosingEvent.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β app.App β
β ββββββββββββββββββββ ββββββββββββββββββββ β
β β Component Factoryβ β Configure β β
β ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ β
β β β β
β β β β
β ββββββββββΌββββββββββ ββββββββββΌββββββββββ β
β β DefinitionRegistryβ β Loader β β
β β SingletonRegistryβ β (args/file/raw) β β
β ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ β
β β β β
β β register β β
β ββββββββββΌββββββββββ ββββββββββΌββββββββββ β
β βComponentDefinitionβ β Binder β β
β ββββββββββ¬ββββββββββ β (viper) β β
β β βββββββββββββββββββββ β
β β create β
β ββββββββββΌββββββββββ β
β β Instance β β
β ββββββββββ¬ββββββββββ β
β β β
β ββββββββββΌβββββββββββββββββββββββββββββββββββββββββββ β
β β PostProcessors β β
β β ββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β’ LoggerAware β β β
β β β β’ ConfigQuoteAware ${...} β β β
β β β β’ ExpressionTagAware #{...} β β β
β β β β’ PropertiesAware prefix β β β
β β β β’ ValueAware value/prop β β β
β β β β’ DependencyAware wire β β β
β β β β’ ConstructorAware func β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Config Flow: Binder β ConfigQuoteAware, PropertiesAware, ValueAware, ExpressionTagAware
Note: The constructor processor is now built into the framework; no separate registration is required.
- Example projects:
test/ioc,test/constructor,test/prop,test/t_yaml - Post processor example:
examples/post_processor/ - Performance tools:
cmd/performance_analyst/ - Unit tests:
unittest/component/builtin_inject/*,unittest/configure/*
Run tests:
go test ./...Contributions via Issues / PRs are welcome. Please ensure go test ./... passes before submitting.