CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-kodein-di--kodein-di

KOtlin DEpendency INjection - A straightforward and yet very useful dependency retrieval container for Kotlin Multiplatform

Pending
Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Advanced dependency injection patterns including constructor injection, external sources, binding search, sub-DI creation, and specialized utilities for complex dependency management scenarios.

Capabilities

Constructor Injection (New Operator)

Automatic constructor parameter injection for creating instances with dependency resolution without explicit binding definitions.

/**
 * Create instance with no-parameter constructor injection
 * @param constructor Constructor function reference
 * @return New instance with all dependencies automatically injected
 */
fun <T> DirectDIAware.new(constructor: () -> T): T

/**
 * Create instance with 1-parameter constructor injection
 * @param constructor Constructor function reference
 * @return New instance with P1 automatically injected via instance<P1>()
 */
fun <P1, T> DirectDIAware.new(constructor: (P1) -> T): T

/**
 * Create instance with 2-parameter constructor injection
 * @param constructor Constructor function reference  
 * @return New instance with P1, P2 automatically injected
 */
fun <P1, P2, T> DirectDIAware.new(constructor: (P1, P2) -> T): T

/**
 * Create instance with 3-parameter constructor injection
 * @param constructor Constructor function reference
 * @return New instance with P1, P2, P3 automatically injected
 */
fun <P1, P2, P3, T> DirectDIAware.new(constructor: (P1, P2, P3) -> T): T

// ... continues up to 22 parameter overloads

/**
 * Exception thrown when a constructor parameter is not used in injection
 * This helps detect unused parameter instances in new operator calls
 */
class DI.UnusedParameterException(message: String, cause: Exception? = null) : RuntimeException(message, cause)

External Sources

Fallback mechanism for dependency resolution when bindings are not found in the container, enabling integration with other DI frameworks or dynamic resolution.

/**
 * External source interface for fallback dependency resolution
 */
interface ExternalSource {
    /**
     * Attempt to provide a factory for the given key when not found in container
     * @param key The binding key that was not found
     * @param context The resolution context
     * @return Factory function or null if this source cannot provide the dependency
     */
    fun <C : Any, A, T : Any> getFactory(
        key: DI.Key<C, A, T>,
        context: C
    ): ((A) -> T)?
}

/**
 * External sources list for registering fallback resolution mechanisms
 * External sources are consulted in order when bindings are not found
 */
val DI.MainBuilder.externalSources: MutableList<ExternalSource>

Sub-DI Creation

Creation of child DI containers that inherit from parent containers while allowing additional bindings and overrides.

/**
 * Create a sub-DI container with additional bindings
 * @param allowSilentOverride Whether to allow implicit binding overrides
 * @param copy Copy strategy for inheriting bindings from parent
 * @param init Configuration block for additional bindings
 * @return New DI container extending the current one
 */
fun DirectDIAware.subDI(
    allowSilentOverride: Boolean = false,
    copy: Copy = Copy.NonCached,
    init: DI.MainBuilder.() -> Unit
): DI

/**
 * Copy strategies for sub-DI creation
 */
sealed class Copy {
    /** Copy no bindings from parent */
    object None : Copy()
    
    /** Copy all bindings from parent */
    object All : Copy()
    
    /** Copy only non-cached bindings (providers, factories) */
    object NonCached : Copy()
}

Factory Currying

Utilities for converting factory functions into provider functions by pre-supplying arguments.

/**
 * Convert a factory function to a provider by currying with an argument
 * @param arg Function that provides the argument for the factory
 * @return Provider function that calls the factory with the supplied argument
 */
fun <A, T> ((A) -> T).toProvider(arg: () -> A): () -> T

Binding Search and Discovery

Advanced binding search capabilities for finding and inspecting bindings within the DI container.

/**
 * Specifications for searching bindings in the container
 */
data class SearchSpecs(
    val contextType: TypeToken<*>? = null,
    val argType: TypeToken<*>? = null,
    val type: TypeToken<*>? = null,
    val tag: Any? = null
)

/**
 * DSL for building search specifications
 */
class SearchDSL {
    /**
     * Search for bindings with specific context type
     */
    fun <C : Any> context(type: TypeToken<C>)
    
    /**
     * Search for bindings with specific argument type
     */
    fun <A> argument(type: TypeToken<A>)
    
    /**
     * Search for bindings with specific return type
     */
    fun <T> type(type: TypeToken<T>)
    
    /**
     * Search for bindings with specific tag
     */
    fun tag(tag: Any)
}

/**
 * DSL for finding bindings in the container
 */
class FindDSL : SearchDSL() {
    // Additional methods for finding specific binding patterns
}

/**
 * Find all bindings matching the search criteria
 * @param f DSL block for specifying search criteria
 * @return List of matching bindings with their keys and context matches
 */
fun DITree.findAllBindings(
    f: FindDSL.() -> Unit
): List<Triple<DI.Key<*, *, *>, DIBinding<*, *, *>, ContextMatch>>

/**
 * Context match information for search results
 */
enum class ContextMatch {
    EXACT,    // Exact context type match
    SUPER,    // Context is a supertype
    SUB       // Context is a subtype
}

Late Initialization Support

Patterns and utilities for delayed DI initialization in scenarios where immediate container creation is not possible.

/**
 * Late-initialized DI container for manual setup scenarios
 */
class LateInitDI : DI {
    /**
     * Base DI instance that must be set before use
     * @throws UninitializedPropertyAccessException if accessed before initialization
     */
    lateinit var baseDI: DI
    
    // All DI operations delegate to baseDI after initialization
}

/**
 * Property delegate for late-initialized DI-aware properties
 */
class LateinitDIProperty<T>(
    private val getDI: () -> DI,
    private val creator: DirectDI.() -> T
) : LazyDelegate<T>

Multi-Argument Support

Advanced binding patterns for functions and factories that require multiple arguments beyond single-parameter factories.

/**
 * Multi-argument factory binding support
 * Allows binding factories that take multiple typed arguments
 */
interface MultiArgFactory<T> {
    fun with(vararg args: Any): T
}

/**
 * Multi-argument binding DSL support
 */
fun <T : Any> DI.Builder.multiArgFactory(
    creator: DirectDI.(Array<Any>) -> T
): MultiArgFactory<T>

Error Information Enhancement

Enhanced error reporting with detailed container information and binding diagnostics.

/**
 * Enhanced exception with detailed binding information
 */
class DI.NotFoundException(
    val key: Key<*, *, *>,
    message: String
) : RuntimeException(message) {
    /** The binding key that was not found */
    val key: Key<*, *, *>
}

/**
 * Exception for dependency cycles with detailed loop information
 */
class DI.DependencyLoopException(message: String) : RuntimeException(message)

/**
 * Exception for binding override conflicts
 */
class DI.OverridingException(message: String) : RuntimeException(message)

/**
 * Exception for search operations that return no results
 */
class DI.NoResultException(
    val search: SearchSpecs,
    message: String
) : RuntimeException(message)

// Error configuration for detailed diagnostics
var DI.MainBuilder.fullDescriptionOnError: Boolean
var DI.MainBuilder.fullContainerTreeOnError: Boolean

Type System Integration

Advanced type handling and generic type preservation for complex dependency scenarios.

/**
 * Typed wrapper for values with their type information
 * @param A Type of the wrapped value
 */
interface Typed<A> {
    /** TypeToken representing the type of the value */
    val type: TypeToken<A>
    
    /** The actual typed value */
    val value: A
}

/**
 * Create a typed wrapper with immediate value
 * @param type TypeToken for the value type
 * @param value The value to wrap
 * @return Typed wrapper containing the value and its type
 */
fun <A> Typed(type: TypeToken<A>, value: A): Typed<A>

/**
 * Create a typed wrapper with lazy value evaluation
 * @param type TypeToken for the value type
 * @param func Function that provides the value when needed
 * @return Typed wrapper with lazy value evaluation
 */
fun <A> Typed(type: TypeToken<A>, func: () -> A): Typed<A>

Container Inspection and Debugging

Utilities for inspecting and debugging DI container state and binding resolution.

/**
 * Container tree interface for inspecting binding structure
 */
interface DITree {
    /**
     * Find bindings matching search criteria
     */
    fun find(search: SearchSpecs): List<DI.Key<*, *, *>>
    
    /**
     * Get all registered bindings
     */
    fun allBindings(): Map<DI.Key<*, *, *>, DIBinding<*, *, *>>
    
    /**
     * Get container description for debugging
     */
    fun containerDescription(): String
}

/**
 * Binding information for diagnostics
 */
data class BindingInfo(
    val key: DI.Key<*, *, *>,
    val binding: DIBinding<*, *, *>,
    val scope: String?,
    val description: String
)

Usage Examples:

// Constructor injection with new operator
class OrderService : DirectDIAware {
    override val directDI: DirectDI = appDirectDI
    
    fun processOrder(order: Order) {
        // Automatic constructor parameter injection
        val validator = new(::OrderValidator) // Injects dependencies automatically
        val processor = new(::PaymentProcessor) // Finds and injects required services
        val emailer = new(::OrderEmailService) // Injects email service and templates
        
        validator.validate(order)
        val payment = processor.process(order.paymentInfo)
        emailer.sendConfirmation(order, payment)
    }
    
    // Multi-parameter constructor injection
    fun createComplexService() {
        val service = new { db: Database, cache: Cache, logger: Logger, config: Config ->
            ComplexService(db, cache, logger, config) // All 4 params auto-injected
        }
    }
}

// External sources for fallback resolution
class SpringIntegrationSource : ExternalSource {
    private val springContext: ApplicationContext = getSpringContext()
    
    override fun <C : Any, A, T : Any> getFactory(
        key: DI.Key<C, A, T>, 
        context: C
    ): ((A) -> T)? {
        return try {
            val bean = springContext.getBean(key.type.jvmType as Class<T>)
            { _ -> bean } // Return factory that ignores argument and returns Spring bean
        } catch (e: NoSuchBeanDefinitionException) {
            null // Let DI continue looking
        }
    }
}

val integratedDI = DI {
    // Regular Kodein-DI bindings
    bind<UserService>() with singleton { UserServiceImpl() }
    
    // Add Spring as fallback
    externalSources.add(SpringIntegrationSource())
    
    // Now can inject Spring beans even if not bound in Kodein-DI
}

// Sub-DI for testing and isolation
class TestingService : DirectDIAware {
    override val directDI = productionDI.direct
    
    fun runTestScenario() {
        // Create test-specific DI with mocks
        val testDI = subDI(copy = Copy.NonCached) {
            bind<EmailService>(overrides = true) with singleton { MockEmailService() }
            bind<PaymentProcessor>(overrides = true) with singleton { TestPaymentProcessor() }
            
            // Additional test-only services
            bind<TestDataProvider>() with singleton { TestDataProviderImpl() }
        }
        
        // Use test DI for this scenario
        val testDirectDI = testDI.direct
        val service = testDirectDI.new(::OrderService) // Uses mocked dependencies
        service.processTestOrder()
    }
}

// Factory currying for partial application
class DataProcessingService : DirectDIAware {
    override val directDI = appDirectDI
    
    fun setupProcessors() {
        // Get factory and curry it with configuration
        val processorFactory = directDI.factory<ProcessingConfig, DataProcessor>()
        
        val standardConfig = ProcessingConfig(threads = 4, batchSize = 100)
        val standardProcessor = processorFactory.toProvider { standardConfig }
        
        val highPerfConfig = ProcessingConfig(threads = 8, batchSize = 200)
        val highPerfProcessor = processorFactory.toProvider { highPerfConfig }
        
        // Now have two providers with different configurations
        processStandardData(standardProcessor())
        processHighVolumeData(highPerfProcessor())
    }
}

// Binding search and discovery
class DIInspector(private val di: DI) {
    fun inspectBindings() {
        val tree = di.container.tree
        
        // Find all singleton bindings
        val singletons = tree.findAllBindings {
            // Search by binding type patterns
        }.filter { (_, binding, _) -> 
            binding.javaClass.simpleName.contains("Singleton")
        }
        
        println("Found ${singletons.size} singleton bindings:")
        singletons.forEach { (key, binding, match) ->
            println("  ${key.description} -> ${binding.description}")
        }
        
        // Find all bindings for a specific type
        val userServiceBindings = tree.find(SearchSpecs(
            type = generic<UserService>(),
            tag = null
        ))
        
        println("UserService bindings: $userServiceBindings")
    }
    
    fun diagnosticReport(): String {
        return try {
            di.container.tree.containerDescription()
        } catch (e: Exception) {
            "Error generating diagnostic report: ${e.message}"
        }
    }
}

// Late initialization patterns
class ApplicationContext {
    private val lateInitDI = LateInitDI()
    
    // Services that depend on DI
    private val userService: UserService by lateInitDI.instance()
    private val configService: ConfigService by lateInitDI.instance()
    
    fun initialize(config: AppConfig) {
        // Set up DI after configuration is available
        lateInitDI.baseDI = DI {
            bind<AppConfig>() with instance(config)
            bind<UserService>() with singleton { UserServiceImpl(instance()) }
            bind<ConfigService>() with singleton { ConfigServiceImpl(instance()) }
        }
        
        // Now property delegates can resolve
        configService.loadConfiguration()
        userService.initializeUserData()
    }
}

// Advanced error handling and diagnostics
class RobustDIService : DIAware {
    override val di = DI {
        fullDescriptionOnError = true // Include full type names in errors
        fullContainerTreeOnError = true // Include all bindings in NotFoundException
        
        bind<PrimaryService>() with singleton { PrimaryServiceImpl() }
        bind<FallbackService>() with singleton { FallbackServiceImpl() }
    }
    
    fun robustOperation() {
        try {
            val service: PrimaryService by instance()
            service.execute()
        } catch (e: DI.NotFoundException) {
            println("Binding not found: ${e.key.fullDescription}")
            println("Available bindings:")
            println(e.message) // Contains full container tree due to config
            
            // Try fallback
            val fallback: FallbackService by instance()
            fallback.execute()
        } catch (e: DI.DependencyLoopException) {
            println("Dependency loop detected: ${e.message}")
            // Handle circular dependency
        }
    }
}

// Multi-argument and complex binding scenarios
val advancedDI = DI {
    // Multi-argument factory using array
    bind<ReportGenerator>() with factory { args: Array<Any> ->
        val format = args[0] as ReportFormat
        val filters = args[1] as List<DataFilter>
        val options = args[2] as ReportOptions
        ReportGeneratorImpl(format, filters, options)
    }
    
    // Typed wrapper for preserving generic information
    bind<Typed<List<User>>>() with singleton {
        Typed(generic<List<User>>()) {
            userRepository.getAllUsers()
        }
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-kodein-di--kodein-di

docs

advanced-features.md

binding-dsl.md

container-configuration.md

direct-access.md

index.md

lazy-property-delegation.md

scoping-and-context.md

tile.json