Domain-driven design using Kotlin
This framework lets you start your coding from domain layer with pure Common Kotlin. Test-driven development is also supported via generated unit test stubs.
The Dokt plugin generates application layer, which follows Command and Query Responsibility Segregation (CQRS) and Event Sourcing (ES) patterns. It can’t generate infrastructure layer :) but you can code it to any platform.
Features
- The domain logic depends on only Common Kotlin.
- Boilerplate code is generated by Dokt Gradle plugin:
- Configures project Gradle, plugin and library dependencies
- Generates application layer code, Kotest unit tests and project documentation
- Runtime is fast as possible
- Generates reflectionless code and slow kotlin-reflect is’t needed.
- Coroutines are utilized in:
- Command handlers (transaction locks only single aggregate and there isn’t evil global sequences).
- Event handlers (outside aggregate)
- Serialization code is generated at compile-time.
The “Hello, World!” tutorial:
- Apply Dokt plugin in the build.gradle.kts file:
plugins {
id("app.dokt") version "0.2.0"
}
- Write domain logic in src/commonMain/kotlin/Hello.kt file:
import app.dokt.Root
import kotlinx.serialization.Serializable
/** Events that Greeter emits */
interface Events {
fun greeted(greeting: String)
}
/** Greeter aggregate root. Identified by UUID (default). */
@Serializable // For unit testing
class Greeter : Root<Events>(), Events {
val greetings = mutableListOf<String>()
/** Greet command handler */
fun greet(who: String) {
if (who.isBlank()) throw IllegalArgumentException("Missing 'who'!")
emit.greeted("Hello, $who!")
}
/** Greeted event handler */
override fun greeted(greeting: String) {
greetings.add(greeting)
}
}
- Run
generateCode
task. - Write unit test in src/commonTest/kotlin/GreeterTest.kt file:
/** Greeter unit tests */
class GreeterTest : GreeterSpec({ // GreeterSpec is generated in previous step.
greet { // Command handler is a generated test context
test("World") { // "World" test case
greeter // Greeter is arranged with a random UUID.
.act { greet("World") } // The command act
.emits(Greeted("Hello, World!")) // Asserts the emitted DTO.
}
}
})
- Run
allTests
and you should pass your test.
Check more examples.
Thanks
Following frameworks have inspired this project a lot:
- AxonFramework by Allard Buijze (the best for JVM)
- EventFlow by Rasmus Mikkelsen (the best for .Net)