State Machines & Actor Pattern

Building Resilient Asynchronous Systems

Why State Machines?

State machines provide explicit, predictable state management for complex asynchronous workflows. They're crucial for:

Finite State Machine (FSM) Basics

Core Concepts

A Finite State Machine consists of:

  1. States: Finite set of possible states (e.g., EMPTY, PENDING, APPROVED)
  2. Events/Messages: Inputs that trigger state transitions
  3. Transitions: Rules for moving from one state to another
  4. Actions: Side effects executed during transitions
  5. Initial State: Starting point of the machine
Campaign Workflow State Machine

stateDiagram-v2 [*] --> EMPTY EMPTY --> PENDING: CreateCampaign PENDING --> WAITING_REVIEW: Campaign Created WAITING_REVIEW --> APPROVED: Review Approved WAITING_REVIEW --> REJECTED: Review Rejected WAITING_REVIEW --> CANCELLED: Cancel PENDING --> CANCELLED: Cancel APPROVED --> [*] REJECTED --> [*] CANCELLED --> [*] note right of EMPTY Initial state No campaign exists end note note right of WAITING_REVIEW Awaiting manual review Can wait hours/days end note note right of APPROVED Terminal state Campaign active end note

State Machine in Python

from enum import Enum, auto

class State(Enum):
    """Explicit state enumeration"""
    EMPTY = auto()
    PENDING = auto()
    WAITING_ON_REVIEW = auto()
    APPROVED = auto()
    REJECTED = auto()
    CANCELLED = auto()

class StateMachine:
    def __init__(self):
        self.state = State.EMPTY
        self.state_history = [(State.EMPTY, datetime.now())]

    def transition_to(self, new_state: State):
        """Explicit state transition with audit trail"""
        old_state = self.state
        self.state = new_state
        self.state_history.append((new_state, datetime.now()))

        logger.info(f"Transition: {old_state.name} → {new_state.name}")

Actor Pattern

What is the Actor Model?

The Actor Model is a concurrent computation model where "actors" are the fundamental units of computation. Each actor:

Actor Model Flow

sequenceDiagram participant Sender as Actor A (Sender) participant Mailbox as Mailbox (Message Queue) participant Actor as Actor B (Receiver) participant State as Private State participant NewActor as Actor C (New Actor) Note over Sender,NewActor: Message Passing (Asynchronous) Sender->>Mailbox: Send Message 1 Sender->>Mailbox: Send Message 2 Sender->>Mailbox: Send Message 3 Note over Mailbox: Messages queued in FIFO order Note over Actor,State: Processing Loop loop One Message at a Time Mailbox->>Actor: Dequeue Message 1 Note over Actor: 1. Receive message, 2. Read state, 3. Execute handler Actor->>State: Read current state State-->>Actor: state = {count: 5} Note over Actor: Process message and make decisions alt Update State Actor->>State: Update state to {count: 6} Note over State: State changed - isolated to this actor end alt Send to Other Actor Actor->>NewActor: Send new message Note over NewActor: Message received in its mailbox end alt Create New Actor Actor->>NewActor: Spawn new actor Note over NewActor: New actor created with own state and mailbox end Note over Actor: Message processed - ready for next Mailbox->>Actor: Dequeue Message 2 Note over Actor: Repeat... end Note over Sender,NewActor: Key Benefits Note over State: Private State - No shared memory, no race conditions, no locks Note over Mailbox: Message Queue - Asynchronous, decoupled, buffered Note over Actor: Sequential Processing - One at a time, predictable, easy to reason about

Actor Model Guarantees

  1. Message Ordering: Messages from Actor A to Actor B arrive in FIFO order
  2. Isolation: Each actor's state is completely private - no shared memory
  3. Sequential Processing: Actor processes exactly one message at a time
  4. Asynchronous: Sending a message is non-blocking - sender continues immediately
  5. Location Transparency: Actor can be local or remote - same interface

Traditional Approach

  • Shared mutable state
  • Locks and mutexes
  • Race conditions
  • Difficult to reason about
  • Hard to test

Actor Model

  • Message passing
  • No shared state
  • No locks needed
  • Easy to reason about
  • Easy to test

Message-Based Architecture

from dataclasses import dataclass
from typing import Callable, Dict

@dataclass
class Message:
    """Base message class"""
    timestamp: datetime = None

@dataclass
class CreateCampaignMsg(Message):
    """Command to create campaign"""
    campaign_name: str
    budget: float
    target_audience: str
    user_id: str

@dataclass
class ReviewReceivedMsg(Message):
    """Event: Review decision received"""
    approved: bool
    reviewer_id: str
    comments: str

class CampaignActor:
    def __init__(self, campaign_id: str):
        self.campaign_id = campaign_id
        self.state = State.EMPTY

        # Map state → message type → handler function
        self.handlers = self._build_command_handlers()

    def _build_command_handlers(self) -> Dict[State, Dict[type, Callable]]:
        """
        Build message routing table

        Only certain messages are valid in each state
        """
        return {
            State.EMPTY: {
                CreateCampaignMsg: self._handle_create_campaign,
            },
            State.WAITING_ON_REVIEW: {
                ReviewReceivedMsg: self._handle_review_received,
                CancelCampaignMsg: self._handle_cancel,
            },
            State.APPROVED: {
                # Terminal state - no transitions allowed
            },
        }

    async def handle_message(self, message: Message) -> Dict[str, Any]:
        """
        Main message handler - routes to appropriate handler
        based on current state and message type
        """
        message_type = type(message)
        state_handlers = self.handlers.get(self.state, {})

        # Validate message is allowed in current state
        if message_type not in state_handlers:
            return {
                'status': 'error',
                'message': f'Invalid message {message_type.__name__} for state {self.state.name}'
            }

        # Route to handler
        handler = state_handlers[message_type]
        return await handler(message)

Complete Implementation

State-Based Message Routing

async def _handle_create_campaign(self, msg: CreateCampaignMsg) -> Dict[str, Any]:
    """
    Handle campaign creation
    State transition: EMPTY → PENDING → WAITING_ON_REVIEW
    """
    logger.info(f"Creating campaign: {msg.campaign_name}")

    # Transition to PENDING
    self._transition_to(State.PENDING)

    # Simulate async work (API calls, DB writes, etc.)
    await asyncio.sleep(0.5)

    # Create campaign entity
    self.campaign = Campaign(
        campaign_id=self.campaign_id,
        campaign_name=msg.campaign_name,
        budget=msg.budget,
        target_audience=msg.target_audience,
        created_by=msg.user_id,
        created_at=datetime.now(),
        status=State.PENDING
    )

    # Transition to next state
    self._transition_to(State.WAITING_ON_REVIEW)

    return {
        'status': 'success',
        'message': 'Campaign created, pending review',
        'current_state': self.state.name
    }

async def _handle_review_received(self, msg: ReviewReceivedMsg) -> Dict[str, Any]:
    """
    Handle review decision
    State transition:
    - WAITING_ON_REVIEW → APPROVED (if approved)
    - WAITING_ON_REVIEW → REJECTED (if rejected)
    """
    logger.info(f"Review: {'APPROVED' if msg.approved else 'REJECTED'}")

    # Update campaign with review info
    self.campaign.review_comments = msg.comments
    self.campaign.reviewed_by = msg.reviewer_id
    self.campaign.reviewed_at = datetime.now()

    if msg.approved:
        self._transition_to(State.APPROVED)
        self.campaign.status = State.APPROVED

        # Activate campaign
        await asyncio.sleep(0.3)

        return {
            'status': 'success',
            'message': 'Campaign approved and activated',
            'current_state': self.state.name
        }
    else:
        self._transition_to(State.REJECTED)
        self.campaign.status = State.REJECTED

        return {
            'status': 'success',
            'message': 'Campaign rejected',
            'current_state': self.state.name,
            'reason': msg.comments
        }

The Akka Actor Model (JVM)

What is Akka?

Akka is the original and most mature actor framework for the JVM, written for Java and Scala. It popularized the actor model for building resilient, distributed systems and inspired many implementations in other languages.

  • Battle-tested in production at scale (LinkedIn, PayPal, Intel)
  • Location transparency - actors can be local or remote
  • Supervision hierarchies for fault tolerance
  • Built-in clustering and sharding
  • Akka Persistence for event sourcing
  • Akka Streams for reactive stream processing
  • Akka Actor in Java

    import akka.actor.typed.*;
    import akka.actor.typed.javadsl.*;
    
    // Message types
    interface CampaignCommand {}
    
    record CreateCampaign(String name, double budget, String targetAudience)
        implements CampaignCommand {}
    
    record ReviewReceived(boolean approved, String reviewerId, String comments)
        implements CampaignCommand {}
    
    record CancelCampaign(String reason) implements CampaignCommand {}
    
    // States
    enum State {
        EMPTY, PENDING, WAITING_ON_REVIEW, APPROVED, REJECTED, CANCELLED
    }
    
    // Actor behavior
    public class CampaignActor extends AbstractBehavior {
        private State state = State.EMPTY;
        private Campaign campaign;
    
        public static Behavior create() {
            return Behaviors.setup(CampaignActor::new);
        }
    
        private CampaignActor(ActorContext context) {
            super(context);
        }
    
        @Override
        public Receive createReceive() {
            // Route messages based on current state
            return switch (state) {
                case EMPTY -> handleEmpty();
                case WAITING_ON_REVIEW -> handleWaitingOnReview();
                case APPROVED, REJECTED, CANCELLED -> handleTerminal();
                default -> Behaviors.same();
            };
        }
    
        private Receive handleEmpty() {
            return newReceiveBuilder()
                .onMessage(CreateCampaign.class, this::onCreateCampaign)
                .build();
        }
    
        private Receive handleWaitingOnReview() {
            return newReceiveBuilder()
                .onMessage(ReviewReceived.class, this::onReviewReceived)
                .onMessage(CancelCampaign.class, this::onCancelCampaign)
                .build();
        }
    
        private Receive handleTerminal() {
            return newReceiveBuilder()
                .onAnyMessage(msg -> {
                    getContext().getLog().warn("Invalid message in terminal state: {}", msg);
                    return Behaviors.same();
                })
                .build();
        }
    
        private Behavior onCreateCampaign(CreateCampaign cmd) {
            getContext().getLog().info("Creating campaign: {}", cmd.name());
    
            state = State.PENDING;
            campaign = new Campaign(cmd.name(), cmd.budget(), cmd.targetAudience());
    
            // Simulate async work
            state = State.WAITING_ON_REVIEW;
    
            getContext().getLog().info("Campaign created, awaiting review");
            return this;
        }
    
        private Behavior onReviewReceived(ReviewReceived cmd) {
            if (cmd.approved()) {
                state = State.APPROVED;
                getContext().getLog().info("Campaign APPROVED");
            } else {
                state = State.REJECTED;
                getContext().getLog().info("Campaign REJECTED: {}", cmd.comments());
            }
            return this;
        }
    
        private Behavior onCancelCampaign(CancelCampaign cmd) {
            state = State.CANCELLED;
            getContext().getLog().info("Campaign CANCELLED: {}", cmd.reason());
            return this;
        }
    }
    

    Akka Typed FSM (Finite State Machine)

    Akka provides a more structured FSM builder that makes state transitions explicit:

    import akka.actor.typed.javadsl.*;
    
    public class CampaignFSM {
        // State data holders
        interface StateData {}
        record EmptyData() implements StateData {}
        record PendingData(Campaign campaign) implements StateData {}
        record ReviewData(Campaign campaign) implements StateData {}
    
        public static Behavior create() {
            return Behaviors.setup(context ->
                new CampaignFSM(context).empty(new EmptyData())
            );
        }
    
        private final ActorContext context;
    
        private CampaignFSM(ActorContext context) {
            this.context = context;
        }
    
        // EMPTY state behavior
        private Behavior empty(EmptyData data) {
            return Behaviors.receive(CampaignCommand.class)
                .onMessage(CreateCampaign.class, cmd -> {
                    context.getLog().info("State: EMPTY → PENDING");
                    Campaign campaign = new Campaign(cmd.name(), cmd.budget(), cmd.targetAudience());
    
                    // Transition to WAITING_ON_REVIEW
                    context.getLog().info("State: PENDING → WAITING_ON_REVIEW");
                    return waitingOnReview(new ReviewData(campaign));
                })
                .build();
        }
    
        // WAITING_ON_REVIEW state behavior
        private Behavior waitingOnReview(ReviewData data) {
            return Behaviors.receive(CampaignCommand.class)
                .onMessage(ReviewReceived.class, cmd -> {
                    if (cmd.approved()) {
                        context.getLog().info("State: WAITING_ON_REVIEW → APPROVED");
                        return approved(data);
                    } else {
                        context.getLog().info("State: WAITING_ON_REVIEW → REJECTED");
                        return rejected(data);
                    }
                })
                .onMessage(CancelCampaign.class, cmd -> {
                    context.getLog().info("State: WAITING_ON_REVIEW → CANCELLED");
                    return cancelled(data);
                })
                .build();
        }
    
        // Terminal states
        private Behavior approved(ReviewData data) {
            context.getLog().info("Campaign approved: {}", data.campaign().getName());
            return Behaviors.stopped(); // Or Behaviors.same() to keep actor alive
        }
    
        private Behavior rejected(ReviewData data) {
            context.getLog().info("Campaign rejected");
            return Behaviors.stopped();
        }
    
        private Behavior cancelled(ReviewData data) {
            context.getLog().info("Campaign cancelled");
            return Behaviors.stopped();
        }
    }
    

    Akka Persistence - Event Sourcing

    Akka Persistence enables crash recovery through event sourcing:

    import akka.persistence.typed.PersistenceId;
    import akka.persistence.typed.javadsl.*;
    
    public class PersistentCampaignActor
        extends EventSourcedBehavior {
    
        public static Behavior create(String campaignId) {
            return Behaviors.setup(context ->
                EventSourcedBehavior.start(
                    new PersistentCampaignActor(
                        PersistenceId.of("Campaign", campaignId)
                    ),
                    context
                )
            );
        }
    
        private PersistentCampaignActor(PersistenceId persistenceId) {
            super(persistenceId);
        }
    
        @Override
        public CampaignState emptyState() {
            // Initial state
            return new CampaignState(State.EMPTY, null);
        }
    
        @Override
        public CommandHandler commandHandler() {
            return newCommandHandlerBuilder()
                // EMPTY state handlers
                .forState(state -> state.currentState() == State.EMPTY)
                    .onCommand(CreateCampaign.class, (state, cmd) ->
                        // Persist event, then update state
                        Effect().persist(
                            new CampaignCreated(cmd.name(), cmd.budget(), cmd.targetAudience())
                        )
                    )
    
                // WAITING_ON_REVIEW state handlers
                .forState(state -> state.currentState() == State.WAITING_ON_REVIEW)
                    .onCommand(ReviewReceived.class, (state, cmd) -> {
                        if (cmd.approved()) {
                            return Effect().persist(new CampaignApproved(cmd.reviewerId()));
                        } else {
                            return Effect().persist(
                                new CampaignRejected(cmd.reviewerId(), cmd.comments())
                            );
                        }
                    })
    
                .build();
        }
    
        @Override
        public EventHandler eventHandler() {
            return newEventHandlerBuilder()
                // Update state based on events
                .forAnyState()
                    .onEvent(CampaignCreated.class, (state, evt) ->
                        new CampaignState(
                            State.WAITING_ON_REVIEW,
                            new Campaign(evt.name(), evt.budget(), evt.targetAudience())
                        )
                    )
                    .onEvent(CampaignApproved.class, (state, evt) ->
                        state.withState(State.APPROVED)
                    )
                    .onEvent(CampaignRejected.class, (state, evt) ->
                        state.withState(State.REJECTED)
                    )
                .build();
        }
    }
    
    // Events - these are persisted and used for recovery
    interface CampaignEvent {}
    record CampaignCreated(String name, double budget, String targetAudience)
        implements CampaignEvent {}
    record CampaignApproved(String reviewerId) implements CampaignEvent {}
    record CampaignRejected(String reviewerId, String comments) implements CampaignEvent {}
    

    Key Akka Concepts

    Concept Description Python Equivalent
    ActorRef Reference to an actor (for sending messages) Actor instance reference
    ActorContext Access to actor system, logging, spawning Self, logger attributes
    Behavior Defines how actor reacts to messages handle_message() method
    Pattern Matching .onMessage(Type.class, handler) type(message) → handler dict
    State Transitions Return new Behavior self.state = new_state
    Persistence EventSourcedBehavior get_snapshot/restore_snapshot
    Supervision Parent monitors child failures Try/except with restart logic

    Akka Supervision Example

    // Parent actor that supervises children
    public class CampaignSupervisor extends AbstractBehavior {
    
        public static Behavior create() {
            return Behaviors.setup(CampaignSupervisor::new);
        }
    
        private CampaignSupervisor(ActorContext context) {
            super(context);
        }
    
        @Override
        public Receive createReceive() {
            return newReceiveBuilder()
                .onMessage(CreateCampaignActor.class, this::onCreateActor)
                .build();
        }
    
        private Behavior onCreateActor(CreateCampaignActor cmd) {
            // Spawn child actor with supervision strategy
            ActorRef child = getContext().spawn(
                Behaviors.supervise(CampaignActor.create())
                    .onFailure(
                        SupervisorStrategy.restart()
                            .withLimit(3, Duration.ofMinutes(1)) // Max 3 restarts per minute
                    ),
                "campaign-" + cmd.campaignId()
            );
    
            // If child fails, supervisor will automatically restart it
            // Child's state is lost unless using persistence
    
            return this;
        }
    }
    

    Akka vs Python Implementation

    Advantages of Akka:

    Python Implementation Benefits:

    Resilience Through State Persistence

    Crash Recovery

    State machines enable resilient systems that can recover from crashes:

    1. Persist state after each transition
    2. On crash: System goes down but state is saved
    3. On recovery: Recreate actor from persisted state
    4. Continue processing: Resume workflow exactly where it left off

    State Persistence Implementation

    class CampaignActorWithPersistence(CampaignActor):
        """Actor with state persistence for crash recovery"""
    
        def __init__(self, campaign_id: str, persisted_state: Optional[Dict] = None):
            super().__init__(campaign_id)
    
            if persisted_state:
                self._restore_from_snapshot(persisted_state)
                logger.info(f"Restored from snapshot, state: {self.state.name}")
    
        def get_snapshot(self) -> Dict:
            """Get current state snapshot for persistence"""
            return {
                'campaign_id': self.campaign_id,
                'state': self.state.name,
                'campaign': self._campaign_to_dict()
            }
    
        def _restore_from_snapshot(self, snapshot: Dict):
            """Restore actor state from snapshot"""
            self.state = State[snapshot['state']]
    
            if snapshot.get('campaign'):
                # Restore campaign data
                c = snapshot['campaign']
                self.campaign = Campaign(
                    campaign_id=c['campaign_id'],
                    campaign_name=c['campaign_name'],
                    budget=c['budget'],
                    target_audience=c['target_audience'],
                    created_by=c['created_by'],
                    created_at=datetime.fromisoformat(c['created_at']),
                    status=State[c['status']]
                )
    
    # Crash recovery example
    async def demo_crash_recovery():
        # 1. Create actor and process messages
        actor = CampaignActorWithPersistence("CAMP-001")
        await actor.handle_message(CreateCampaignMsg(...))
    
        # 2. Save state before crash
        snapshot = actor.get_snapshot()
    
        # 3. 💥 CRASH! Actor destroyed
        del actor
    
        # 4. Recover from snapshot
        recovered_actor = CampaignActorWithPersistence(
            "CAMP-001",
            persisted_state=snapshot
        )
    
        # 5. Continue processing
        await recovered_actor.handle_message(ReviewReceivedMsg(...))
        # ✓ Workflow continues seamlessly!
    

    Best Practices

    Design Principles

    1. Explicit States: Use enums, not strings or magic numbers
    2. Type-Safe Messages: Use dataclasses for message types
    3. One Message at a Time: Process messages sequentially
    4. Immutable Messages: Messages should be immutable
    5. State History: Keep audit trail of state transitions
    6. Error Handling: Reject invalid messages gracefully
    7. Idempotency: Handle duplicate messages safely

    Common Pitfalls

    Real-World Use Cases

    Use Case States Benefits
    Order Processing CART → CHECKOUT → PAYMENT → PROCESSING → SHIPPED → DELIVERED Track order lifecycle, prevent invalid transitions, enable refunds
    User Onboarding NEW → EMAIL_VERIFIED → PROFILE_COMPLETED → ACTIVE Guide users through steps, skip completed steps on return
    CI/CD Pipeline QUEUED → BUILDING → TESTING → DEPLOYING → DEPLOYED Visualize pipeline, handle failures, enable rollbacks
    Video Processing UPLOADED → TRANSCODING → THUMBNAIL → READY → PUBLISHED Retry failed steps, track progress, parallel processing
    Document Approval DRAFT → REVIEW → APPROVED/REJECTED → PUBLISHED Audit trail, multiple reviewers, version control

    Interactive Demo

    Campaign State Machine Simulator

    Experience the state machine in action. Try different workflows and see how invalid transitions are prevented.

    EMPTY
    No campaign created yet
    Waiting for action...

    Framework Implementations

    Akka (JVM)

    The original actor framework for JVM languages (Java, Scala)

    • Mature and battle-tested
    • Distributed actors
    • Supervision hierarchies
    • Event sourcing support

    Ray (Python)

    Distributed computing framework with actor support

    • Python-native
    • ML/AI workloads
    • Automatic scaling
    • GPU support

    Orleans (C#)

    Microsoft's virtual actor framework

    • .NET ecosystem
    • Automatic activation
    • Azure integration
    • Grain persistence

    The Evolution: Temporal Workflows

    Beyond State Machines: Modern Workflow Orchestration

    While state machines and actors teach fundamental principles, Temporal represents the evolution of these patterns for production systems. It provides the same resilience benefits with dramatically simpler code.

    State Machine Approach

    // 200+ lines of code
    class CampaignActor {
      State state = EMPTY;
    
      commandHandler() {
        forState(EMPTY)
          .match(CreateMsg, this::handleCreate)
        forState(WAITING_REVIEW)
          .match(ReviewMsg, this::handleReview)
      }
    
      handleCreate(msg) {
        // Manual state transition
        state = PENDING;
        // Manual retry logic
        try {
          createCampaign()
        } catch {
          // retry logic...
        }
        state = WAITING_REVIEW;
      }
    }

    Temporal Approach

    // 50 lines of code
    @workflow.defn
    class CampaignWorkflow {
      ReviewStatus reviewStatus;
    
      @workflow.run
      async run(request) {
        // Step 1: Create (auto-retry)
        await activities.createCampaign(request);
    
        // Step 2: Wait (durable, can wait days!)
        await workflow.wait_condition(
          () => reviewStatus != null
        );
    
        // Step 3: Process decision
        if (reviewStatus == APPROVED) {
          await activities.markApproved();
        } else {
          await activities.markRejected();
        }
      }
    
      @workflow.signal
      reviewSignal(status) {
        reviewStatus = status;
      }
    }

    Key Advantages of Temporal

    Feature State Machine Temporal
    Code Complexity 200+ lines, scattered logic 50 lines, linear flow
    State Management Manual tracking, transitions Automatic event sourcing
    Persistence Manual snapshots, restore Built-in, zero config
    Retries Manual implementation Built-in with backoff
    Long Waits Complex (timers + state) Simple: wait_condition()
    Visibility Custom monitoring Built-in Web UI
    Testing Mock messages, states Test like normal code

    Migration Strategy: Wrapper Pattern

    You don't need to rewrite everything at once! The InfoQ talk showed a wrapper pattern for gradual migration:

    1. Phase 1: Wrap existing actors
      • Keep actor code unchanged
      • Wrap with Temporal workflow
      • Get durability + visibility immediately
      • Risk: Low, Time: Days
    2. Phase 2: Gradual refactor
      • Move handlers one at a time
      • Test each change independently
      • Run old and new in parallel
      • Risk: Medium, Time: Weeks
    3. Phase 3: Pure Temporal
      • All logic in workflows/activities
      • Remove actor infrastructure
      • Clean, maintainable code
      • Risk: Low, Time: Months

    When to Use Each Approach

    Use State Machines For:

    • Learning fundamentals
    • Understanding actor model
    • Interview preparation
    • Simple state transitions
    • Full control needed

    Use Temporal For:

    • Production systems
    • Long-running workflows
    • Complex business processes
    • Need auto-recovery
    • Simpler maintenance

    The Bottom Line

    State machines teach you WHY these patterns exist.

    Temporal gives you the TOOLS to use them in production.

    Both are built on the same principles (event sourcing, message passing, durable state), but Temporal abstracts away the complexity. Learn state machines to understand the fundamentals, use Temporal to build real systems.

    Code examples: See temporal_campaign_workflow.py and temporal_wrapper_migration.py for complete Python implementations.

    Interview Questions

    Common Questions

    1. Q: When would you use a state machine over simple if/else logic?
      A: When you have complex state transitions, need audit trails, require crash recovery, or want to prevent invalid state transitions by design.
    2. Q: How do actors prevent race conditions?
      A: Actors process messages one at a time sequentially. No locks needed because there's no shared mutable state - each actor owns its state exclusively.
    3. Q: How do you handle long-running operations in an actor?
      A: Use async/await for I/O operations. For CPU-intensive work, delegate to a worker pool and have it send a completion message back to the actor.
    4. Q: What happens if a message handler fails?
      A: Implement supervision strategies: retry with backoff, move to error state, send to dead letter queue, or restart actor from last known good state.
    5. Q: How do you test state machines?
      A: Test each state transition independently. Verify invalid messages are rejected. Test edge cases like duplicate messages, timeouts, and concurrent operations.
    6. Q: What's the difference between state machines and workflow orchestration like Temporal?
      A: Both provide durable, recoverable execution. State machines require manual state management and persistence. Temporal abstracts this away with automatic event sourcing and simpler code. State machines teach fundamentals; Temporal provides production tooling.
    7. Q: How would you migrate from actors to Temporal?
      A: Use a gradual wrapper pattern: (1) Wrap existing actors with Temporal workflows to get durability immediately, (2) Gradually refactor internals one handler at a time, (3) Eventually move to pure Temporal workflows. This minimizes risk and allows parallel operation.

    Additional Resources

    Further Learning