Signals

Lesson 10: Interactive Workflow Patterns

Master interactive workflow patterns using Temporal signals and queries to build responsive, long-running workflows that can react to external events and provide real-time status information.


Objective

By the end of this lesson, you will understand:

  • Signals vs Queries - when to use each pattern
  • Signal handling patterns for event-driven workflows
  • Interactive approval workflows with external decision makers
  • Long-running workflow patterns with external events
  • State management in signal-driven workflows
  • Best practices for responsive workflow design

1. Signals vs Queries

Signals: Changing Workflow State

@WorkflowInterface
interface OrderTrackingWorkflow {
    @WorkflowMethod
    fun trackOrder(orderId: String): OrderResult

    // Signals modify workflow state
    @SignalMethod
    fun updateShippingStatus(update: ShippingUpdate)

    @SignalMethod
    fun processCustomerRequest(request: CustomerRequest)

    @SignalMethod
    fun handleException(exception: OrderException)
}

Signals enable external systems to send events to running workflows


Queries: Reading Workflow State

@WorkflowInterface
interface OrderTrackingWorkflow {
    // Queries read current state without modification
    @QueryMethod
    fun getCurrentStatus(): OrderStatus

    @QueryMethod
    fun getShippingHistory(): List<ShippingEvent>

    @QueryMethod
    fun getEstimatedDelivery(): LocalDateTime?

    @QueryMethod
    fun getCustomerRequests(): List<CustomerRequest>
}

Queries provide real-time visibility into workflow state without affecting execution


Key Differences

Aspect Signals Queries
Purpose Modify workflow state Read workflow state
Durability Persisted in workflow history Not persisted
Timing Asynchronous Synchronous
Replay Executed during replay Not executed during replay
Side Effects Can trigger activities Read-only operations

Choose signals for state changes, queries for state inspection


2. Signal Handling Patterns

Event-Driven State Machine

class ApprovalWorkflowImpl : ApprovalWorkflow {

    private var state = ApprovalState.PENDING
    private var approvals = mutableListOf<Approval>()
    private var rejections = mutableListOf<Rejection>()
    private val requiredApprovers = mutableSetOf<String>()

    override fun processApprovalRequest(request: ApprovalRequest): ApprovalResult {
        val logger = Workflow.getLogger(this::class.java)

        // Initialize state
        requiredApprovers.addAll(request.requiredApprovers)

        // Send notifications
        notificationActivity.notifyApprovers(request)

        // Wait for signals or timeout
        val approved = Workflow.await(Duration.ofDays(7)) {
            when (state) {
                ApprovalState.APPROVED -> true
                ApprovalState.REJECTED -> true
                ApprovalState.PENDING -> false
            }
        }
        // Continued on next slide...

Approval State Handling

        return when (state) {
            ApprovalState.APPROVED -> {
                val executionResult = executionActivity.executeRequest(request)
                ApprovalResult.approved(request.id, approvals, executionResult)
            }
            ApprovalState.REJECTED -> {
                cleanupActivity.cleanup(request.id)
                ApprovalResult.rejected(request.id, rejections)
            }
            ApprovalState.PENDING -> {
                cleanupActivity.cleanup(request.id)
                ApprovalResult.timedOut(request.id)
            }
        }
    }

    @SignalMethod
    override fun approve(approverEmail: String, comment: String) {
        if (state != ApprovalState.PENDING) return

        val approval = Approval(approverEmail, comment, Instant.now())
        approvals.add(approval)
        requiredApprovers.remove(approverEmail)

        // Check if all required approvals received
        if (requiredApprovers.isEmpty()) {
            state = ApprovalState.APPROVED
        }
    }
    // Continued on next slide...

Signal Handler Implementation

    @SignalMethod
    override fun reject(approverEmail: String, reason: String) {
        if (state != ApprovalState.PENDING) return

        val rejection = Rejection(approverEmail, reason, Instant.now())
        rejections.add(rejection)
        state = ApprovalState.REJECTED
    }
}

Key Signal Patterns:

  • State validation in signal handlers
  • Conditional logic based on current state
  • Collection management for tracking multiple events
  • State transitions triggered by signals

Signal Buffering and Ordering

class OrderProcessingWorkflowImpl : OrderProcessingWorkflow {

    private val pendingUpdates = mutableListOf<StatusUpdate>()
    private var isProcessingUpdates = false

    override fun processOrder(orderId: String): OrderResult {
        // Main workflow logic
        val result = executeOrderProcessing(orderId)

        // Process any buffered updates
        processPendingUpdates()

        return result
    }

    @SignalMethod
    override fun updateOrderStatus(update: StatusUpdate) {
        // Buffer updates if workflow is busy
        pendingUpdates.add(update)

        // Trigger processing if not already running
        if (!isProcessingUpdates) {
            isProcessingUpdates = true
            processUpdatesAsync()
        }
    }
    // Continued on next slide...

Async Signal Processing

    private fun processUpdatesAsync() {
        // Process updates in a separate workflow "thread"
        Async.procedure {
            while (pendingUpdates.isNotEmpty()) {
                val update = pendingUpdates.removeFirst()
                processStatusUpdate(update)
            }
            isProcessingUpdates = false
        }
    }
}

Signal Processing Patterns:

  • Buffering for high-frequency signals
  • Async processing to avoid blocking main workflow
  • Ordering guarantees for dependent signals
  • State consistency during concurrent updates

💡 Key Takeaways

What You've Learned:

  • Signals enable interactive workflows that respond to external events
  • Queries provide real-time observability without affecting execution
  • Event-driven state machines handle complex approval flows
  • Signal buffering manages high-frequency updates
  • Workflow.await() blocks until conditions are met

results matching ""

    No results matching ""