The Three Components in Detail
Think of an actor like a person sitting in a private office with a locked door and an inbox slot on the door. You cannot walk in and rearrange their desk (no direct state access). You can only slide a note through the slot (send a message). They will read it when they are ready, act on it, and possibly slide notes under other people's doors in response.
1. The Mailbox
When you send a message to an actor, the message is placed in the actor's mailbox — a FIFO queue. The actor picks messages off the queue one at a time. If the actor is busy processing message #5, messages #6, #7, and #8 wait patiently. This is what makes the model safe: the actor's state is only ever touched by one piece of code at a time.
Sender ──► [msg1, msg2, msg3] ──► Actor processes msg1
▲ │
mailbox queue modifies state
│
sends reply
2. Private State
The actor's state is just regular variables — a counter, a map, a list, a database connection. The difference is that these variables are completely hidden. No getter, no setter, no shared reference. The only way to observe or change the state is to send the actor a message and wait for a reply.
# Pseudocode: Actor internal state
class WalletActor:
state = {"balance": 500.00} # PRIVATE — nobody else touches this
def on_message(self, msg):
if msg.type == "DEPOSIT":
self.state["balance"] += msg.amount
self.reply({"status": "ok", "new_balance": self.state["balance"]})
elif msg.type == "GET_BALANCE":
self.reply({"balance": self.state["balance"]})
3. Behavior (Message Handling)
The behavior is the switch statement (or pattern match) that decides what to do for each message type. Crucially, the behavior can also change — an actor can switch to a different behavior after processing a message. This is how actors model state machines. A ConnectionActor might have a disconnected behavior and a connected behavior, and switch between them based on messages received.
Fire and Forget: Asynchronous Messaging
This is the most important shift in thinking when moving to the Actor Model. In object-oriented programming, calling a method is synchronous — you call it, you block, you wait for the return value:
// OOP: Synchronous — caller blocks until result is returned
int balance = walletService.getBalance(userId);
// Nothing below this line runs until getBalance() returns
In the Actor Model, sending a message is asynchronous — you send it and immediately continue:
// Akka (Scala): Asynchronous — fire and forget
walletActor ! GetBalance(userId)
// This line runs immediately, before walletActor processes the message
If you need a response, you send a message that includes a reply-to address (a reference to yourself), and the receiving actor sends a message back when it is ready. This is called the ask pattern and it returns a Future or Promise that resolves when the reply arrives.
The implication: you can never assume a message was processed just because you sent it. This is not a bug — it is the model. It forces you to design systems that are explicitly resilient to partial failures.