KState

KState is a lightweight, zero-dependency Kotlin library designed for mission-critical state management in financial and enterprise systems. Built for zero-error-tolerance environments with comprehensive testing, thread safety, and immutable state patterns.

Core Features

KState provides four main capabilities for building robust, production-grade applications:

๐ŸŽฏ 1. Immutable State Containers

Simple, thread-safe containers for managing immutable state with validation and atomic updates.

๐Ÿ”„ 2. Event-Driven State Machines

Declarative DSL for complex state machines with guarded transitions, lifecycle hooks, and side effects.

  • stateMachine() - Full-featured state machine builder

  • Guards and validation for controlled transitions

  • Entry/exit actions and side effects

  • Multiple event handlers with short-circuiting

๐Ÿ”€ 3. Saga Orchestration (Distributed Transactions)

Built-in saga pattern for managing multi-step workflows with automatic compensation.

  • sagaExecutor() - Saga orchestration with LIFO compensation

  • Automatic rollback on failure

  • Monitoring and observability hooks

  • Integration with state machines via EventHandler.saga()

๐Ÿ‘€ 4. Observer Pattern & Validation

Rich notification system and validation framework for reactive programming.

  • StateObserver - State change observers with old/new state access

  • StateValidator - Custom validation rules enforced before transitions

  • Audit trails and telemetry integration

Why Choose KState?

Zero Dependencies, Maximum Reliability

  • Only requires Kotlin standard library - no external framework dependencies

  • Perfect for domain layer architecture and microservices

  • Minimal runtime footprint with maximum performance

Financial-Grade Safety

  • Thread-safe by design with atomic operations

  • Immutable state prevents accidental corruption

  • Comprehensive validation at every state transition

  • 270 test cases covering edge cases, concurrency, saga compensation, and security

Developer Experience

  • Intuitive DSL for state machines and saga orchestration

  • Type-safe state transitions with compile-time guarantees

  • Rich observer pattern for reactive programming

  • Built-in saga pattern for distributed transactions with automatic compensation

Quick Start Examples

Requirements

  • JDK 17 or newer for builds and local development

  • Kotlin 2.1.0 or newer in consuming projects (matches the library target)

  • Gradle 8.x or Maven 3.8+

Installation

Gradle (Kotlin DSL):

repositories {
mavenCentral()
}

dependencies {
implementation("ca.acendas:kstate:1.6.1")
}

Gradle (Groovy DSL):

repositories {
mavenCentral()
}

dependencies {
implementation 'ca.acendas:kstate:1.6.1'
}

Maven:

<dependency>
<groupId>ca.acendas</groupId>
<artifactId>kstate</artifactId>
<version>1.6.1</version>
</dependency>

Simple State Container

data class CounterState(val count: Int = 0)

val container = stateContainer(CounterState())
val result = container.updateState { state ->
state.copy(count = state.count + 1)
}

Saga Orchestration (Distributed Transactions)

Execute multi-step workflows with automatic compensation on failure. Perfect for microservices, payment processing, and distributed transactions.

data class OrderContext(val items: List<String>, val amount: Double)
data class OrderResult(val orderId: String, val confirmed: Boolean)

val orderSaga = sagaExecutor<OrderContext, OrderResult> {
// Step 1: Reserve inventory
step("reserve-inventory") { context ->
val reservationId = inventoryService.reserve(context.items)
ReservationResult(reservationId)
}
compensate { result ->
inventoryService.release(result.reservationId)
}

// Step 2: Charge payment
step("charge-payment") { context ->
val transactionId = paymentService.charge(context.amount)
PaymentResult(transactionId)
}
compensate { result ->
paymentService.refund(result.transactionId)
}

// Step 3: Confirm order
step("confirm-order") { context ->
OrderResult("order-${System.currentTimeMillis()}", true)
}

// Monitor saga events
monitor { event ->
logger.info("Saga event: $event")
}
}

// Execute the saga
val result = orderSaga.execute(OrderContext(listOf("item1", "item2"), 99.99))
when (result) {
is SagaResult.Completed -> println("Order confirmed: ${result.value.orderId}")
is SagaResult.Aborted -> println("Order cancelled, compensated successfully")
is SagaResult.CompensationFailure -> println("Critical failure - manual intervention required")
}

Key Saga Features:

  • Automatic LIFO (Last-In-First-Out) compensation on failure

  • Thread-safe execution with monitoring hooks

  • Type-safe step definitions and result handling

  • Integration with state machines for complex workflows

State Machine DSL

The stateMachine builder is the primary entry point for declaratively modelling event-driven workflows. It supports guarded transitions, lifecycle hooks, side effects, observer notifications, and remains coroutine-friendly and thread-safe.

Basic Usage

sealed interface PaymentState {
data class Pending(val amount: BigDecimal) : PaymentState
data class Authorized(val amount: BigDecimal, val authCode: String) : PaymentState
data class Captured(val amount: BigDecimal, val transactionId: String) : PaymentState
}

sealed interface PaymentEvent : StateEvent {
data class Authorize(val authCode: String) : PaymentEvent
data class Capture(val transactionId: String) : PaymentEvent
}

val paymentProcessor = stateMachine<PaymentState, PaymentEvent> {
initialState = PaymentState.Pending(BigDecimal("99.99"))

state<PaymentState.Pending> {
on<PaymentEvent.Authorize> { event ->
transitionWith { state, _ ->
PaymentState.Authorized(state.amount, event.authCode)
}
}
}

state<PaymentState.Authorized> {
on<PaymentEvent.Capture> { event ->
transitionWith { state, _ ->
PaymentState.Captured(state.amount, event.transactionId)
}
}
}
}

paymentProcessor.start()
paymentProcessor.send(PaymentEvent.Authorize("AUTH-123"))
paymentProcessor.send(PaymentEvent.Capture("TXN-456"))

Coroutine Integration

import kotlinx.coroutines.*

sealed interface FileProcessorState {
data object Idle : FileProcessorState
data class Processing(val fileName: String, val progress: Int = 0) : FileProcessorState
data class Completed(val fileName: String, val result: String) : FileProcessorState
data class Failed(val fileName: String, val error: String) : FileProcessorState
}

sealed interface FileProcessorEvent : StateEvent {
data class StartProcessing(val fileName: String) : FileProcessorEvent
data class ProgressUpdate(val progress: Int) : FileProcessorEvent
data class ProcessingComplete(val result: String) : FileProcessorEvent
data class ProcessingFailed(val error: String) : FileProcessorEvent
}

val processorScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

// Domain-specific service responsible for the actual IO work
val fileService: FileService = obtainFileService()

val machine = stateMachine<FileProcessorState, FileProcessorEvent> {
initialState = FileProcessorState.Idle

state<FileProcessorState.Idle> {
on<FileProcessorEvent.StartProcessing> { event ->
sideEffect {
processorScope.launch {
fileService.process(event.fileName)
}
}
transitionTo(FileProcessorState.Processing(event.fileName))
}
}

state<FileProcessorState.Processing> {
on<FileProcessorEvent.ProgressUpdate> { event ->
transitionWith { state, _ -> state.copy(progress = event.progress) }
}

on<FileProcessorEvent.ProcessingComplete> { event ->
transitionTo(FileProcessorState.Completed(state.fileName, event.result))
}

on<FileProcessorEvent.ProcessingFailed> { event ->
transitionTo(FileProcessorState.Failed(state.fileName, event.error))
}
}
}

This DSL keeps transitions explicit, enables domain-specific validation through guards, and plays well with structured concurrency when orchestrating asynchronous workflows. Use your own domain binding to supply obtainFileService() in the above snippet.

Observable State Container

val container = observableStateContainer(CounterState())

container.observe { oldState, newState ->
println("Count changed: ${oldState.count} -> ${newState.count}")
}

container.updateState { it.copy(count = it.count + 1) }

State Validation

Enforce business rules before accepting state changes:

data class AccountState(val balance: Double, val overdraftLimit: Double)

val validator = StateValidator<AccountState> { state ->
when {
state.balance < -state.overdraftLimit ->
ValidationResult.Invalid(listOf("Balance exceeds overdraft limit"))
state.overdraftLimit < 0 ->
ValidationResult.Invalid(listOf("Overdraft limit cannot be negative"))
else -> ValidationResult.Valid
}
}

val account = stateContainer(
initialState = AccountState(balance = 1000.0, overdraftLimit = 500.0),
validator = validator
)

// This update will be rejected by the validator
val result = account.updateState {
it.copy(balance = -600.0) // Exceeds overdraft limit
}

when (result) {
is StateUpdateResult.Success -> println("Update successful")
is StateUpdateResult.ValidationFailure ->
println("Validation failed: ${result.errors}")
}

Guards and Conditional Transitions

Control state transitions with guard conditions:

sealed interface OrderState {
data class Draft(val items: List<String>) : OrderState
data class Submitted(val items: List<String>, val total: Double) : OrderState
data object Cancelled : OrderState
}

sealed interface OrderEvent : StateEvent {
data object Submit : OrderEvent
data object Cancel : OrderEvent
}

val orderMachine = stateMachine<OrderState, OrderEvent> {
initialState = OrderState.Draft(emptyList())

state<OrderState.Draft> {
on<OrderEvent.Submit> {
// Guard: only allow submission if cart has items
guard { state -> state.items.isNotEmpty() }

transitionWith { state, _ ->
val total = state.items.size * 10.0 // Simple calculation
OrderState.Submitted(state.items, total)
}
}

on<OrderEvent.Cancel> {
transitionTo(OrderState.Cancelled)
}
}
}

Event-Driven State Container

Simpler event handling without full state machine complexity:

sealed interface CounterEvent : StateEvent {
data object Increment : CounterEvent
data object Decrement : CounterEvent
data class Set(val value: Int) : CounterEvent
}

val counter = eventDrivenStateContainer<CounterState, CounterEvent>(
initialState = CounterState(0)
) { currentState, event ->
when (event) {
is CounterEvent.Increment -> currentState.copy(count = currentState.count + 1)
is CounterEvent.Decrement -> currentState.copy(count = currentState.count - 1)
is CounterEvent.Set -> currentState.copy(count = event.value)
}
}

counter.start()
counter.send(CounterEvent.Increment)
counter.send(CounterEvent.Set(42))

Thread-Safe Concurrent Operations

Explicit thread-safe state management for high-concurrency scenarios:

data class InventoryState(val stock: Map<String, Int>)

val inventory = threadSafeStateContainer(
InventoryState(mapOf("item1" to 100, "item2" to 50))
)

// Safe from multiple threads
runBlocking {
launch {
inventory.updateState { state ->
val current = state.stock["item1"] ?: 0
state.copy(stock = state.stock + ("item1" to current - 1))
}
}

launch {
inventory.updateState { state ->
val current = state.stock["item2"] ?: 0
state.copy(stock = state.stock + ("item2" to current - 1))
}
}
}

Saga with State Machine Integration

Combine sagas with state machines for complex workflows:

sealed interface CheckoutState {
data object Idle : CheckoutState
data class Processing(val orderId: String) : CheckoutState
data class Completed(val orderId: String) : CheckoutState
data class Failed(val error: String) : CheckoutState
}

sealed interface CheckoutEvent : StateEvent {
data class StartCheckout(val items: List<String>, val amount: Double) : CheckoutEvent
}

val checkoutMachine = stateMachine<CheckoutState, CheckoutEvent> {
initialState = CheckoutState.Idle

state<CheckoutState.Idle> {
on<CheckoutEvent.StartCheckout> {
saga<CheckoutState, CheckoutEvent, OrderContext, OrderResult> {
context { state, event ->
OrderContext(event.items, event.amount)
}

step("reserve-inventory") { context ->
inventoryService.reserve(context.items)
}
compensate { result ->
inventoryService.release(result.reservationId)
}

step("process-payment") { context ->
paymentService.charge(context.amount)
}
compensate { result ->
paymentService.refund(result.transactionId)
}

onComplete { result ->
CheckoutState.Completed(result.orderId)
}

onFailure { error ->
CheckoutState.Failed(error.exception.message ?: "Unknown error")
}
}
}
}
}

Next steps

  • Explore the DSL reference for stateMachine, stateContainer, and observer utilities in the side navigation.

  • Combine these APIs with your validation logic to enforce domain rules before state transitions are committed.

  • Run ./gradlew test (or mvn test) to confirm the integration is wired correctly in your environment.

Main API Entry Points

State Management

State Machines & Workflows

Core Types & Utilities

  • StateValidator - Define custom validation rules for state transitions

  • StateObserver - React to state changes with observer pattern

  • SagaMonitor - Monitor saga execution with lifecycle events

  • SagaResult - Result types for saga execution (Completed, Aborted, CompensationFailure)

  • StateEvent - Base interface for all state machine events

Architecture & Concepts

  • Immutable state shapes, atomic operations, and thread-safe containers keep state transitions deterministic.

  • Validation pipelines (StateValidator, ValidationResult) enforce domain rules before accepting new state snapshots.

  • Event-driven builders scope handlers, side effects, and lifecycle hooks (onEnter, guard, sideEffect) to specific states.

  • Namespace layout mirrors the architecture: core for containers, events for the state machine DSL, observers for change notifications, validation for business rule enforcement, and utils for supporting extensions.

Production Readiness

  • High Performance: >10,000 operations/second, <1ms latency

  • Comprehensive Testing: 270 test cases covering state machines, sagas, concurrency, and edge cases

  • Financial Grade: Suitable for zero-error-tolerance environments with saga compensation guarantees

  • Thread Safety: All operations are safe for concurrent access

  • Zero Dependencies: Perfect for domain layer architecture and microservices

  • Observability: Observer hooks, saga monitors, audit trails, and side effects integrate with logging/telemetry

  • Validation: Custom validators enforce business rules before state transitions commit

  • Saga Support: LIFO compensation, monitoring, and failure recovery for distributed transactions

Clean Architecture Integration

  • Domain-Centric Design: Because KState ships as pure Kotlin with zero runtime dependencies, it satisfies Uncle Bob's dependency ruleโ€”your domain layer depends on nothing but your own models and the Kotlin standard library.

  • Use Case Coordination: Wrap important domain workflows (aggregates, policies, sagas) in stateMachine builders to express use case orchestration without bleeding infrastructure concerns into the domain.

  • Interactor-Friendly: stateContainer and observableStateContainer compose neatly with application services or interactors, allowing immutable state updates that can be unit-tested in isolation.

  • Boundary Isolation: Emit domain events from state transitions, then let outer layers translate them into messaging, persistence, or UI updates. Nothing in KState requires Android, Spring, or other frameworks.

  • Testing in Isolation: The comprehensive test APIs and absence of platform dependencies mean you can run domain-layer tests on the JVM or in multiplatform setups with no mock-heavy infrastructure.

Operational runbooks (release management, internal tooling, contributor process) are available to partners through private channels and are intentionally omitted from this public package reference.

For comprehensive type-level details, browse the API Reference via the left-hand navigation.

Packages

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard