A tiny, dependency‑light Command Bus for Kotlin/JVM. It helps you model application behavior as Commands handled by dedicated Handlers, with optional Middleware to intercept, log, validate, or transform execution. Results are returned in a simple wrapper so your application code stays explicit and testable.
What this library is for
- Encapsulate actions as Command objects with a single handler each
- Centralize dispatching through a CommandBus
- Compose cross‑cutting concerns using Middleware (e.g., logging, metrics, tracing, validation)
Highlights
- Minimal surface: Command, CommandHandler, CommandBus (with SimpleCommandBus implementation), CommandMiddleware, DomainEvent, CommandResult
- Straightforward generics for typed results
- No reflection, no magic — just Kotlin
Tech stack
- Language: Kotlin (JVM)
- Build: Gradle (Wrapper configured)
- Static analysis: Detekt (with formatting rules)
- Utilities: kotlin-result (for ergonomic success/error handling)
- Toolchain: Java 21 (configured via Gradle toolchains; Foojay resolver plugin)
Getting started
- Add dependency This project is currently set up as a Gradle Kotlin project. If you consume it as a module in a multi‑project build, add it as a dependency in your app module:
dependencies {
implementation(project(":kommand"))
}If you publish it to your internal repository, the coordinates are based on the current build file: group: com.ps name: kommand version: 1.0-SNAPSHOT
- Define your Command and Handler
// A command returning a String
data class Greet(val name: String) : Command<String>
class GreetHandler : CommandHandler<Greet, String> {
override fun handle(command: Greet): CommandResult<String> =
CommandResult(Ok("Hello, ${'$'}{command.name}!"))
}- Optionally add Middleware
class LoggingMiddleware : CommandMiddleware {
override fun <R> invoke(
command: Command<R>,
next: (Command<R>) -> CommandResult<R>
): CommandResult<R> {
println("Executing command: ${'$'}command")
return next(command)
}
}- Create the bus and execute
val bus = SimpleCommandBus(
handlers = mapOf(
// NOTE: SimpleCommandBus maps handlers by command INSTANCE (not type).
// For this minimal implementation you must register the specific instance you will execute.
// See the note below for alternatives.
Greet("World") to GreetHandler()
),
middlewares = listOf(LoggingMiddleware())
)
// Execute now returns the full CommandResult, including the Result and any events
val result = bus.execute(Greet("World"))
println(result.result) // -> Ok(Hello, World!)
println(result.events) // -> [Logged(name=LoggingMiddleware handled Greet)] if your middleware adds eventsBuild
- Using the Gradle wrapper: ./gradlew build (ensure the wrapper is executable on your system)
- Kotlin/JVM toolchain is pinned to Java 21 via Gradle toolchains; the Foojay resolver plugin helps locate/install matching JDKs automatically.
Why Kommand?
- Keep business logic explicit and decoupled
- Make cross‑cutting concerns composable with middleware
- Encourage testable, intention‑revealing code
Notes and design trade‑offs
- Handler lookup key:
SimpleCommandBuskeeps things tiny by using the concrete command instance as the key in itshandlersmap:Map<Command<*>, CommandHandler<..., ...>>. If you prefer type‑based routing (e.g., byKClass<out Command<*>>), you can adapt/extend the bus easily. - Execute return value:
CommandBus.executereturns the fullCommandResult<R>(containing aResult<R, CommandError>andevents). This lets callers inspect success/error and emitted events explicitly. - Events: Handlers return domain events alongside the result in
CommandResult.events. Middleware can publish them to a bus of your choice or aggregate them for later processing.