di

package
v0.0.0-...-2170ac4 Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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:

  1. Services are registered with provider functions
  2. Provider functions declare dependencies as parameters
  3. During Initialize(), the container invokes providers in dependency order
  4. Dependencies are automatically injected based on parameter types
  5. 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

  1. Use SetupContainer for standard applications
  2. Register all services before calling Initialize()
  3. Call Initialize() once during application startup
  4. Retrieve services once and pass them to components
  5. Avoid calling Get() in hot paths (cache service references)
  6. Use interfaces for service types to enable testing
  7. 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

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

func ProvideLogger() (*logrus.Logger, error)

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.

func NewApp

func NewApp(baseDir string) (*App, error)

NewApp builds the core application graph using explicit constructor chaining.

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

func NewAppContainer(app *App) Container

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

func SetupContainer(baseDir string) (Container, error)

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.

Jump to

Keyboard shortcuts

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