Skip to content

dokt/dokt

Repository files navigation

Domain-driven design using Kotlin

GitHub license 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:

  1. Apply Dokt plugin in the build.gradle.kts file:
plugins {
    id("app.dokt") version "0.2.0"
}
  1. 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)
    }
}
  1. Run generateCode task.
  2. 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.
        }
    }
})
  1. Run allTests and you should pass your test.

Check more examples.

Thanks

Following frameworks have inspired this project a lot: