Modules

Group related services into reusable modules. Libraries ship a module, applications include it — with optional overrides.

What are modules?

A Kontainer module is a reusable block of service definitions. Instead of copying service registrations across projects, you define them once in a module and include it.

Modules are useful for:

  • Library authors — bundle your library's services into a module users can drop in
  • Large applications — organize services by feature area
  • Customization — provide sensible defaults that users can override

Defining a simple module

import io.peekandpoke.ultra.kontainer.module

// Some services
interface Storage {
    val name: String
}

class FileStorage : Storage {
    override val name = "FileStorage"
}

class Database(val storage: Storage)

// Define the module
val databaseModule = module {
    singleton(Database::class)
    singleton(Storage::class, FileStorage::class)
}

Using a module

val blueprint = kontainer {
    module(databaseModule)
}

val kontainer = blueprint.create()
println(kontainer.get(Database::class).storage.name)  // "FileStorage"

Overriding module services

Any service defined by a module can be overridden. Just register the same type after including the module:

class MemoryStorage : Storage {
    override val name = "MemoryStorage"
}

val blueprint = kontainer {
    module(databaseModule)

    // Override the Storage implementation
    singleton(Storage::class, MemoryStorage::class)
}

val kontainer = blueprint.create()
println(kontainer.get(Database::class).storage.name)  // "MemoryStorage"

Parameterized modules

Modules can accept parameters — up to 5 — for configuration at inclusion time:

class Service(val sum: Int)

// Module with three parameters
val configModule = module { a: Int, b: Int, c: Int ->
    instance(Service(a + b + c))
}

val blueprint = kontainer {
    module(configModule, 1, 10, 100)
}

val kontainer = blueprint.create()
println(kontainer.get(Service::class).sum)  // 111

Composing modules

Modules can include other modules, enabling layered architectures:

val coreModule = module {
    singleton(Counter::class)
}

val appModule = module {
    module(coreModule)
    singleton(MyService::class)
}

val blueprint = kontainer {
    module(appModule)
}