Skip to content

Experimental Inara bindings - #65

Draft
rocketraman wants to merge 4 commits into
masterfrom
rg/inara-di
Draft

rocketraman wants to merge 4 commits into
masterfrom
rg/inara-di

Conversation

@rocketraman

@rocketraman rocketraman commented Nov 9, 2026 •

Copy link
Copy Markdown
Owner

Kodein's authors are sunsetting Kodein in favour of Inara, a compile-time successor that runs as a KSP processor. This adds experimental Inara bindings. If they work out, we'll deprecate the Kodein bindings and point users to Inara.

What's here

  • An inara flavor in configureDiVariants(), published as <module>-inara like the -kodein and -metro coordinates. Every module with Kodein bindings has one.
  • Each flavor is a @DIModule (BootableModule, ConfigModule, EnvironmentModule, JdbcDataSourceModule, FlywayMigrationModule, ExposedDatabaseModule). It comes with a builder overload of the same name, which binds all of the module's members from what it needs, e.g. JdbcDataSourceModule(SqliteConfigSource, HikariPool(), bootable). Each builder takes the BootableModule as its context, typed only as ConfigModule and AppServiceAcceptor: it reads its config from it and adds its services to it. KSP runs on the flavor's source set only, so the generated builders ship in the -inara jar.
  • The app composes the built modules into its own @DIModule(composed = true) and binds only its own members (composition).
  • examples/wiring-inara and examples/server-ktor-db/inara, and Inara cases in boot's wiring tests.
  • An "Inara (experimental)" section in docs/wiring.adoc, plus README and CHANGELOG entries.
@DIModule(composed = true)
interface AppModule : BootableModule {
  val greetingConfig: GreetingConfig
  val greetingService: GreetingService
}

fun main() = boot {
  val bootable = BootableModule(HopliteConfigModule())
  val app = AppModule(
    bootableModule = bootable,
    greetingConfig = bootable.config("greeting"),
    greetingService = Binding.single { GreetingService(greetingConfig) },
  )
  app.addAppService { app.greetingService }
  app.bootable
}

How Kodein's features map to Inara

  • Multibindings. Inara has none, by design. Each module's builder takes an AppServiceAcceptor, which BootableModule implements, and adds its own services to it (after this gist). The app adds only its own, with app.addAppService { … }. The set lives in each BootableModule instance, not in a global, so tests stay isolated. Forgetting one still compiles, and that service never starts. Adding a service after the set is read throws. A compile-time locator (proposal) would replace it.
  • Optional bindings. Where Kodein uses instanceOrNull, the builder takes a nullable parameter: BootableModule(configModule) reads the tiebreaker from config, BootableModule() falls back to an EmptyConfigModule, and startOrder and tiebreaker parameters set them in code.
  • No diamonds. A composed module rejects shared ancestors, so only BootableModule extends ConfigModule (composed modules nest), and the app's module gets the config through it. The other modules take their context and the JdbcDataSourceModule as parameters.
  • Consumers need KSP and a Kotlin 2.3+ compiler, because the runtime has Kotlin 2.4 metadata. Inara's first release may move to 2.5.

Not covered

The database modules' own tests still exercise only Kodein and Metro. The env, MySQL and PostgreSQL Inara flavors compile, but nothing runs them.

Kodein's authors are sunsetting it in favour of Inara, a compile-time
successor run by KSP. Add an inara flavor to configureDiVariants(),
published as <module>-inara like the kodein and metro ones. KSP runs
Inara's processor on the flavor's source set only, and the sources jar
ships the generated builders.

Each Bootable module is a @DIModule with a builder overload of the
same name. The application composes the modules into its own
@DIModule(composed = true), as Inara's maintainer suggested.

- boot-config-common: ConfigModule, the ConfigModule.config(prefix)
  helper, and EmptyConfigModule, whose EmptyConfigBinder fails every
  read
- boot-config-hoplite: HopliteConfigModule(), like Kodein's
  hopliteConfigModule
- boot: BootableModule, composed over ConfigModule, taking the start
  order and tiebreaker as parameters, and reading the tiebreaker from
  config when there is one
- boot: AppServiceAcceptor and LifecycleControllerAcceptor. Inara has
  no multibindings, so other modules' builders add their services to
  the BootableModule through these, and BootableModule runs them. The
  contributions live in the module instance, not in a global.
- Name the files' JVM classes so they don't clash with the Kodein
  flavor's
- boot's wiring tests cover the inara defaults, bindings, and config
Add inara flavors to boot-env, boot-db-jdbc, the JDBC driver modules,
boot-db-pool-hikari, boot-db-flyway, and boot-db-exposed, named like
their Kodein counterparts.

- JdbcDataSourceModule(configSource, pool, context) takes a
  JdbcConfigSource and a JdbcPool, as Kodein imports a driver and a
  pool: SqliteConfigSource, MysqlConfigSource,
  PostgresqlConfigSource, JdbcGenericConfigSource, and
  HikariPool(customizers).
- The builders take one context, typed C : ConfigModule,
  C : AppServiceAcceptor (or LifecycleControllerAcceptor for env). The
  app passes its BootableModule, and the builders see only the config
  and the acceptor.
- The modules don't extend each other, as composed modules reject
  diamonds: Flyway and Exposed take the JdbcDataSourceModule as a
  parameter.
server-ktor-db/inara keeps its own code in an EntriesModule, built
like the library modules, and AppModule only composes, as the Kodein
example imports entriesModule. IDEA's Gradle settings gain both
example projects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant