Skip to content

Latest commit

 

History

History
208 lines (157 loc) · 6.24 KB

File metadata and controls

208 lines (157 loc) · 6.24 KB

Kotlin Idioms

The Kotlin SDK uses standard Kotlin patterns wherever possible instead of custom APIs.

Java SDK Kotlin SDK
void method() suspend fun method()
Async.function(() -> ...) async { ... }
Promise.allOf(...).get() awaitAll(d1, d2)
Workflow.sleep(duration) delay(duration)
Workflow.newDetachedCancellationScope() withContext(NonCancellable)
Duration.ofSeconds(30) 30.seconds
Optional<T> T?

Suspend Functions

Workflows and activities use suspend fun for natural coroutine integration:

// Method annotations are optional - use only when customizing names
@WorkflowInterface
interface OrderWorkflow {
    suspend fun processOrder(order: Order): OrderResult

    suspend fun updatePriority(priority: Priority)  // Signal handler

    suspend fun addItem(item: OrderItem): Boolean    // Update handler

    val status: OrderStatus  // Query handler - queries are NOT suspend (synchronous)
}

// Use annotations only when customizing names
@WorkflowInterface
interface CustomNameWorkflow {
    @WorkflowMethod(name = "ProcessOrder")
    suspend fun processOrder(order: Order): OrderResult

    @SignalMethod(name = "update-priority")
    suspend fun updatePriority(priority: Priority)

    @UpdateMethod(name = "add-item")
    suspend fun addItem(item: OrderItem): Boolean

    @QueryMethod(name = "get-status")
    val status: OrderStatus
}

@ActivityInterface
interface OrderActivities {
    suspend fun validateOrder(order: Order): Boolean

    suspend fun chargePayment(order: Order): PaymentResult
}

Note: Query methods are never suspend because queries must return immediately without blocking.

Coroutines and Concurrency

Use standard kotlinx.coroutines patterns for parallel execution:

override suspend fun processOrder(order: Order): OrderResult = coroutineScope {
    // Parallel execution using standard async
    val validation = async { KWorkflow.executeActivity(OrderActivities::validateOrder, options, order) }
    val inventory = async { KWorkflow.executeActivity(OrderActivities::checkInventory, options, order) }

    // Wait for all - standard awaitAll
    val (isValid, hasInventory) = awaitAll(validation, inventory)

    if (!isValid || !hasInventory) {
        return@coroutineScope OrderResult(success = false)
    }

    // Sequential execution
    val charged = KWorkflow.executeActivity(OrderActivities::chargePayment, options, order)
    val shipped = KWorkflow.executeActivity(OrderActivities::shipOrder, options, order)

    OrderResult(success = true, trackingNumber = shipped)
}
Kotlin Pattern Purpose
coroutineScope { } Structured concurrency - if one fails, all cancel
async { } Start parallel operation, returns Deferred<T>
awaitAll(d1, d2) Wait for multiple deferreds
delay(duration) Temporal timer (deterministic)

Cancellation

Use standard Kotlin cancellation patterns:

override suspend fun processOrder(order: Order): OrderResult {
    return try {
        doProcessOrder(order)
    } catch (e: CancellationException) {
        // Workflow was cancelled - run cleanup in non-cancellable context
        withContext(NonCancellable) {
            KWorkflow.executeActivity(OrderActivities::releaseInventory, options, order)
            KWorkflow.executeActivity(OrderActivities::refundPayment, options, order)
        }
        throw e  // Re-throw to propagate cancellation
    }
}
Kotlin Pattern Java SDK Equivalent
catch (e: CancellationException) CancellationScope failure callback
withContext(NonCancellable) { } Workflow.newDetachedCancellationScope()
coroutineScope { } Workflow.newCancellationScope()
isActive / ensureActive() CancellationScope.isCancelRequested()

Timeouts

Use withTimeout for deadline-based cancellation:

override suspend fun processWithDeadline(order: Order): OrderResult {
    return withTimeout(1.hours) {
        // Everything here cancels if it takes > 1 hour
        KWorkflow.executeActivity(OrderActivities::validateOrder, options, order)
        KWorkflow.executeActivity(OrderActivities::chargePayment, options, order)
        OrderResult(success = true)
    }
}

// Or get null instead of exception
val result = withTimeoutOrNull(30.minutes) {
    KWorkflow.executeActivity(OrderActivities::slowOperation, options, data)
}

Kotlin Duration

Use kotlin.time.Duration for readable time expressions:

import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.hours

val options = KActivityOptions(
    startToCloseTimeout = 30.seconds,
    scheduleToCloseTimeout = 5.minutes,
    heartbeatTimeout = 10.seconds
)

delay(1.hours)
withTimeout(30.minutes) { ... }

Null Safety

Nullable types replace Optional<T>:

// KWorkflowInfo uses nullable instead of Optional
val info = KWorkflow.info
val parentId: String? = info.parentWorkflowId  // null if no parent

// Activity heartbeat details
val progress = KActivity.executionContext.heartbeatDetails<Int>()
val startIndex = progress ?: 0  // Elvis operator for default

This eliminates .orElse(null), .isPresent, and other Optional ceremony.

Data Classes

Use Kotlin data classes with @Serializable for workflow inputs/outputs:

@Serializable
data class Order(
    val id: String,
    val customerId: String,
    val items: List<OrderItem>,
    val priority: Priority = Priority.NORMAL
)

@Serializable
data class OrderResult(
    val success: Boolean,
    val trackingNumber: String? = null,
    val errorMessage: String? = null
)

@Serializable
enum class Priority { LOW, NORMAL, HIGH }

Data classes provide:

  • Automatic equals(), hashCode(), toString()
  • copy() for creating modified instances
  • Destructuring: val (id, customerId) = order

Related


Next: Configuration