Domain-driven design using Kotlin
This framework uses CQRS and ES patterns and is coded using Multiplatform Kotlin. It's inspired by AxonFramework and EventFlow. Domain logic can be at the client side and/or it can be used from the server.
Features
- Supports JS and JVM platforms
- Fast as possible
kotlin-reflect
not needed- Dependency injection at build time
- Proguard support
- Commands are handled simultaneously. Transaction locks only single aggregate by ID and there isn't global sequences.
- Asynchronous event handling using Coroutines
- Kotlin Serialization
- Fast compile-time serialization
- Storage size is minimal when used Protocol Buffers
- Code generation with Gradle plugin
- Ktor support
- Server-side authorization
- Kotest support
Currently, this framework is at early development stage :(
Learn by simple example
- Write your bounded context code first. For example:
/** Sales context **/
package com.example.sales
// Value objects
data class OrderLine(val prod: UByte, val price: Float, val qty: UShort = 1u) {
val empty get() = qty == UShort.MIN_VALUE
val total get() = price * qty.toFloat()
}
data class VatNo(val countryCode: String, val no: Int) {
init {
if (countryCode.length != 2 || no.toString().length != 8)
throw InvalidVatNo(this)
}
override fun toString() = "$countryCode-$no"
}
// Domain exceptions
data class BannedCustomer(val customer: VatNo) : Exception()
data class InvalidVatNo(val vatNo: VatNo) : Exception()
interface SalesBans : Set<VatNo> // Domain service
interface Events { // Domain events as interface methods
fun quotationOffered(customer: VatNo, lines: List<OrderLine>)
}
/** Sales order aggregate root entity **/
class Order(val emit: Events) : Events {
companion object { lateinit var bans: SalesBans }
// Public accessors are safe to use and allows snapshotting
lateinit var customer: VatNo
var lines = listOf<OrderLine>()
// Command handler for business logic
fun requestForQuotation(customer: VatNo, lines: List<OrderLine>) {
if (customer in bans) throw BannedCustomer(customer)
emit.quotationOffered(customer, lines.filter { it.empty })
}
// Event handler to alter the state
override fun quotationOffered(customer: VatNo, lines: List<OrderLine>) {
this.customer = customer
this.lines += lines
}
}
- Following application classes are generated based on your context code before build:
- Aggregate
- Aggregate factory
- Aggregate root entity test stub
- Application service (based on commands)
- Domain command value objects
- Domain configurator
- Domain event value objects
- Domain service test stub and possibly in-memory implementation
- Now you can create unit tests for the context. Skipping this now for simplicity.
- Implement your interfaces and create custom event handlers in the infrastructure layer:
// Domain service implementation
class SalesBansImpl(vararg ban: VatNo) : SalesBans, Set<VatNo> by ban.toSet()
object QuotationSender : EventHandler<QuotationOffered>(QuotationOffered::class) {
override suspend fun handle(event: QuotationOffered, message: EventMessage) {
// ...create PDF and send it via email...
}
}
- Test your implementations.
- Build the application with Dukt libraries:
dukt-app
Mandatory application base classes command and event processing- Separate package for each Event store implementation
- That's it! Now application is ready for testing and deploying.
Planned later
- Saga support
- Support for other platforms (Android, iOS, Linux, macOS, tvOS, watchOS, Wasm, Windows)
- Code migration from/to other frameworks/languages