Documentation
¶
Overview ¶
Package di provides dependency injection container management for opencenter-cli.
This package implements a lightweight dependency injection (DI) container that manages service lifecycle, dependency resolution, and singleton registration. It provides a single point of initialization for all major components, eliminating duplicate service registration code and ensuring consistent dependency management.
Key Features ¶
- Singleton service registration with automatic dependency resolution
- Type-safe service retrieval using reflection
- Automatic initialization of all registered services
- Clear error messages for registration and initialization failures
- Thread-safe service access after initialization
Service Registration Pattern ¶
Services are registered as singletons with provider functions:
container := di.NewContainer()
// Register service with no dependencies
container.Singleton("ErrorHandler", func() (errors.ErrorHandler, error) {
return errors.NewDefaultErrorHandlerWithoutMasking(), nil
})
// Register service with dependencies (automatically injected)
container.Singleton("FileSystem", func(errorHandler errors.ErrorHandler) (fs.FileSystem, error) {
return fs.NewDefaultFileSystem(errorHandler), nil
})
// Initialize all services
if err := container.Initialize(); err != nil {
log.Fatal(err)
}
Usage Examples ¶
Basic container setup:
func main() {
// Create and initialize container with all services
container, err := di.SetupContainer("/path/to/base/dir")
if err != nil {
log.Fatalf("failed to setup container: %v", err)
}
// Retrieve services
fs, err := container.Get("FileSystem")
if err != nil {
log.Fatalf("failed to get FileSystem: %v", err)
}
fileSystem := fs.(fs.FileSystem)
// Use the service
data, err := fileSystem.ReadFile("/path/to/config.yaml")
if err != nil {
log.Fatal(err)
}
}
Custom container setup:
func setupCustomContainer() (di.Container, error) {
container := di.NewContainer()
// Register your services
if err := container.Singleton("MyService", func() (*MyService, error) {
return NewMyService(), nil
}); err != nil {
return nil, err
}
// Initialize
if err := container.Initialize(); err != nil {
return nil, err
}
return container, nil
}
Service with multiple dependencies:
container.Singleton("ConfigManager", func(
fs fs.FileSystem,
errorHandler errors.ErrorHandler,
pathResolver *paths.PathResolver,
) (*config.ConfigManager, error) {
return config.NewConfigManager(fs, errorHandler, pathResolver), nil
})
Dependency Resolution ¶
The container automatically resolves dependencies:
- Services are registered with provider functions
- Provider functions declare dependencies as parameters
- During Initialize(), the container invokes providers in dependency order
- Dependencies are automatically injected based on parameter types
- Circular dependencies are detected and reported as errors
Example dependency chain:
ErrorHandler (no dependencies)
↓
FileSystem (depends on ErrorHandler)
↓
PathResolver (depends on FileSystem)
↓
ConfigManager (depends on FileSystem, ErrorHandler, PathResolver)
SetupContainer Function ¶
The SetupContainer function provides a pre-configured container with all core services registered:
container, err := di.SetupContainer(baseDir) // Container includes: // - ErrorHandler: Structured error handling // - FileSystem: Safe file operations // - PathResolver: Path resolution for clusters // - Logger: Logging infrastructure // - ConfigManager: Configuration management // - ErrorFormatter: User-friendly error formatting
Additional services can be registered after setup:
container, err := di.SetupContainer(baseDir)
if err != nil {
log.Fatal(err)
}
// Register additional service
container.Singleton("MyService", func(fs fs.FileSystem) (*MyService, error) {
return NewMyService(fs), nil
})
Error Handling ¶
The container provides clear error messages for common issues:
Duplicate registration:
err := container.Singleton("FileSystem", provider1)
err = container.Singleton("FileSystem", provider2)
// Error: service FileSystem already registered
Missing dependency:
container.Singleton("ServiceA", func(serviceB *ServiceB) (*ServiceA, error) {
return NewServiceA(serviceB), nil
})
err := container.Initialize()
// Error: initializing service ServiceA: service ServiceB not registered
Initialization failure:
container.Singleton("Database", func() (*Database, error) {
return nil, errors.New("connection failed")
})
err := container.Initialize()
// Error: initializing service Database: connection failed
Thread Safety ¶
The container is thread-safe after initialization:
- Service registration must happen before Initialize()
- Initialize() must be called from a single goroutine
- After Initialize(), Get() is thread-safe for concurrent access
- All registered services are singletons (single instance)
Best Practices ¶
- Use SetupContainer for standard applications
- Register all services before calling Initialize()
- Call Initialize() once during application startup
- Retrieve services once and pass them to components
- Avoid calling Get() in hot paths (cache service references)
- Use interfaces for service types to enable testing
- Provide clear error messages in provider functions
Testing with DI Container ¶
For testing, create a minimal container with only required services:
func setupTestContainer(t *testing.T) di.Container {
container := di.NewContainer()
// Register minimal services for testing
container.Singleton("ErrorHandler", func() (errors.ErrorHandler, error) {
return errors.NewDefaultErrorHandlerWithoutMasking(), nil
})
container.Singleton("FileSystem", func(eh errors.ErrorHandler) (fs.FileSystem, error) {
return fs.NewDefaultFileSystem(eh), nil
})
if err := container.Initialize(); err != nil {
t.Fatalf("failed to initialize test container: %v", err)
}
return container
}
Or use mock services:
container.Singleton("FileSystem", func() (fs.FileSystem, error) {
return &MockFileSystem{}, nil
})
Performance ¶
The DI container has minimal overhead:
- Service registration: <0.1ms per service
- Initialize(): ~1-5ms depending on service count
- Get(): <0.01ms (simple map lookup after initialization)
The initialization overhead is negligible compared to typical application startup time, and the runtime overhead is effectively zero.
Future Extensions ¶
The container is designed to support future enhancements:
- Scoped services (per-request lifecycle)
- Factory services (new instance per Get())
- Service decorators and middleware
- Lazy initialization (initialize on first Get())
- Service health checks
- Graceful shutdown hooks
These features will be added as needed in future phases of the refactoring.
Index ¶
- func ProvideAuditLogger() (*security.AuditLogger, error)
- func ProvideBootstrapService(pathResolver *paths.PathResolver, validator *validation.ValidationEngine) (*cluster.BootstrapService, error)
- func ProvideCommandRunner(sanitizer security.CommandSanitizer) (security.CommandRunner, error)
- func ProvideCommandSanitizer() (security.CommandSanitizer, error)
- func ProvideConfigManager() (*config.ConfigManager, error)
- func ProvideConfigureService(pathResolver *paths.PathResolver, validator *validation.ValidationEngine, ...) (*cluster.ConfigureService, error)
- func ProvideCredentialMasker() (security.CredentialMasker, error)
- func ProvideErrorFormatter() (ui.ErrorFormatter, error)
- func ProvideInitService(pathResolver *paths.PathResolver, validator *validation.ValidationEngine, ...) (*cluster.InitService, error)
- func ProvideInputValidator(auditLogger *security.AuditLogger) (security.InputValidator, error)
- func ProvideLogger() (*logrus.Logger, error)
- func ProvidePathResolver(baseDir string) (*paths.PathResolver, error)
- func ProvideSetupService(pathResolver *paths.PathResolver, validator *validation.ValidationEngine) (*cluster.SetupService, error)
- func ProvideValidateService(pathResolver *paths.PathResolver, validator *validation.ValidationEngine, ...) (*cluster.ValidateService, error)
- func ProvideValidationEngine() (*validation.ValidationEngine, error)
- type App
- type Container
- type DIContainer
- func (c *DIContainer) Initialize() error
- func (c *DIContainer) Register(name string, constructor interface{}) error
- func (c *DIContainer) Resolve(name string) (interface{}, error)
- func (c *DIContainer) ResolveAs(name string, target interface{}) error
- func (c *DIContainer) Shutdown() error
- func (c *DIContainer) Singleton(name string, constructor interface{}) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ProvideAuditLogger ¶
func ProvideAuditLogger() (*security.AuditLogger, error)
func ProvideBootstrapService ¶
func ProvideBootstrapService( pathResolver *paths.PathResolver, validator *validation.ValidationEngine, ) (*cluster.BootstrapService, error)
ProvideBootstrapService creates a new BootstrapService with dependencies. Requirements: 19.2, 2.1.5
func ProvideCommandRunner ¶
func ProvideCommandRunner(sanitizer security.CommandSanitizer) (security.CommandRunner, error)
func ProvideCommandSanitizer ¶
func ProvideCommandSanitizer() (security.CommandSanitizer, error)
func ProvideConfigManager ¶
func ProvideConfigManager() (*config.ConfigManager, error)
ProvideConfigManager creates a new ConfigManager instance. Requirements: 19.2, 1.3
func ProvideConfigureService ¶
func ProvideConfigureService( pathResolver *paths.PathResolver, validator *validation.ValidationEngine, configManager *config.ConfigManager, ) (*cluster.ConfigureService, error)
ProvideConfigureService creates a new ConfigureService with dependencies.
func ProvideCredentialMasker ¶
func ProvideCredentialMasker() (security.CredentialMasker, error)
func ProvideErrorFormatter ¶
func ProvideErrorFormatter() (ui.ErrorFormatter, error)
ProvideErrorFormatter creates a new ErrorFormatter instance. Requirements: 19.2
func ProvideInitService ¶
func ProvideInitService( pathResolver *paths.PathResolver, validator *validation.ValidationEngine, configManager *config.ConfigManager, ) (*cluster.InitService, error)
ProvideInitService creates a new InitService with dependencies. Requirements: 19.2, 2.1.2
func ProvideInputValidator ¶
func ProvideInputValidator(auditLogger *security.AuditLogger) (security.InputValidator, error)
func ProvideLogger ¶
ProvideLogger creates a new logger instance. Requirements: 19.2
func ProvidePathResolver ¶
func ProvidePathResolver(baseDir string) (*paths.PathResolver, error)
ProvidePathResolver creates a new PathResolver instance. Requirements: 19.2, 1.1
func ProvideSetupService ¶
func ProvideSetupService( pathResolver *paths.PathResolver, validator *validation.ValidationEngine, ) (*cluster.SetupService, error)
ProvideSetupService creates a new SetupService with dependencies. Requirements: 19.2, 2.1.4
func ProvideValidateService ¶
func ProvideValidateService( pathResolver *paths.PathResolver, validator *validation.ValidationEngine, configManager *config.ConfigManager, ) (*cluster.ValidateService, error)
ProvideValidateService creates a new ValidateService with dependencies. Requirements: 19.2, 2.1.3
func ProvideValidationEngine ¶
func ProvideValidationEngine() (*validation.ValidationEngine, error)
ProvideValidationEngine creates a new ValidationEngine with registered validators. Requirements: 19.2, 1.2
Types ¶
type App ¶
type App struct {
BaseDir string
ErrorHandler errors.ErrorHandler
FileSystem fs.FileSystem
PathResolver *paths.PathResolver
Logger *logrus.Logger
ConfigManager *config.ConfigManager
ValidationEngine *validation.ValidationEngine
ErrorFormatter ui.ErrorFormatter
AuditLogger *security.AuditLogger
InputValidator security.InputValidator
CredentialMasker security.CredentialMasker
CommandSanitizer security.CommandSanitizer
CommandRunner security.CommandRunner
InitService *cluster.InitService
ConfigureService *cluster.ConfigureService
ValidateService *cluster.ValidateService
SetupService *cluster.SetupService
BootstrapService *cluster.BootstrapService
}
App is the typed runtime graph used by the CLI critical path.
type Container ¶
type Container interface {
// Register registers a constructor function for a named component
Register(name string, constructor interface{}) error
// Resolve resolves a component by name and returns it
Resolve(name string) (interface{}, error)
// ResolveAs resolves a component and assigns it to the target pointer
ResolveAs(name string, target interface{}) error
// Singleton registers a constructor that will be called once and cached
Singleton(name string, constructor interface{}) error
// Initialize initializes all registered singletons
Initialize() error
// Shutdown cleans up all components
Shutdown() error
}
Container manages component lifecycle and dependencies. Requirements: 18.1, 18.2, 19.1, 19.2, 19.3, 19.4
func NewAppContainer ¶
NewAppContainer exposes the typed runtime graph behind the legacy Container interface.
func NewContainer ¶
func NewContainer() Container
NewContainer creates a new dependency injection container.
func SetupContainer ¶
SetupContainer creates and configures a new DI container with all major components. Requirements: 5.2, 5.3, 5.4, 5.5, 19.2 Note: This registers the core components that are currently implemented. Additional components will be registered as they are implemented in other phases.
type DIContainer ¶
type DIContainer struct {
// contains filtered or unexported fields
}
DIContainer is the default implementation of Container.
func (*DIContainer) Initialize ¶
func (c *DIContainer) Initialize() error
Initialize initializes all registered singletons.
func (*DIContainer) Register ¶
func (c *DIContainer) Register(name string, constructor interface{}) error
Register registers a constructor function for a named component. The constructor can have dependencies as parameters, which will be resolved automatically.
func (*DIContainer) Resolve ¶
func (c *DIContainer) Resolve(name string) (interface{}, error)
Resolve resolves a component by name and returns it.
func (*DIContainer) ResolveAs ¶
func (c *DIContainer) ResolveAs(name string, target interface{}) error
ResolveAs resolves a component and assigns it to the target pointer.
func (*DIContainer) Shutdown ¶
func (c *DIContainer) Shutdown() error
Shutdown cleans up all components.
func (*DIContainer) Singleton ¶
func (c *DIContainer) Singleton(name string, constructor interface{}) error
Singleton registers a constructor that will be called once and cached.