This is a study note of the Akka persistence. It is based on the Akka persistence documents.
An event sourced actor, also known as a persistent actor, uses persistence to recover its state from restart, by a supervisor or a manual stop-start, or migratioin within a cluster. Events, not the current state of the actor, are persisted by event sourcing. States are recovered by event replaying of the full history or from a snapshot.
An event sourced actor is an instance of
EeventSourcedBehavior that has at least four fields: a
PersistenceId instance, an
commandHandler and an
eventHandler. The received messages are commands. The typical activities are:
- Intially, an even sourced actor is created with an
- When receiving a command, the
Effect, often by calling
Effect.persist(event)to persist an event. A successful persistence triggers the
eventHandleris called either after a successful persistence or after a event replaying when the actor restarts. The
eventHandlertakes the current state and the event and produces a new state.
PersistenceId is the unique id in event journal and snapstore. It is often used with Cluster Sharding to ensure there is only one active entity for each
PersistenceId(entityId). Here the
entityId is the business domain identifier of the entity. But the
entityId might not be unique for different types of entities and should include the entity type to be unique. Cluster sharding uses
PersistenceId.apply() method to constructe a unique persistence id from a
entityTypeHint|entityId where the
| is the default separator.
Command Handler and Effects
A command handler takes current
State and the incoming
Command to produce an
Effect directive that defines what event or events, if any, to persist. Effects are created using one of the
Effect factory methods from the following list:
persist: persist one or more events atomically.
none: no event to be persisted.
unhandled: not supported in current state.
stop: stop this actor.
stash: stash the current command.
unstashAll: process the commands sinces the last
reply: send a reply message to an
Effects can be chained by
Effect.persist(...).thenRun... to run side effects after the primary effect. The
thenRun call register callbacks for side effects and uses the new state, i.e., the state after event handler is called with the previous state and new event. In place of
thenRun, you can call
Side effect are executed sequentially on an at-most-once basis and will be skipped if the persist fails. However, when run it in the
RecoveryCompleted signal handler, a side effect may run more than once.
If database doesn’t support atomic persistence of multiple events, an
EventRejectedExecption is throwed and can be handled with a supervior.
When an event has been persisted successfully, an event handler applies the event to the current state. The same event hanlder is used when the actor recovers the current state after restarting. The event handler must only update the actor state (ideally, an immutable type) and never perform side effects. Side effects, such as sending a message or stoping the actor etc., should only be performed in
thenRun/thenStop/thenUnstashAll from the command handler after event persistence.
During recovery, only perform side effects from the
RecoveryCompleted singal handler after recovery.
A regular actor processes a new message and returns a new
Behavior used for next message. However, the persistent actor needs to recover its state from database by replaying events after an auto restart or a manual stop-start.
In persistent actor, both command handler and event handler takes a state parameter and can act on the command and event based on the current state.
This pattern is common because the status of command often matters to the sender. The
EventSourcedBehavior.withEnforcedReplies(...) method can be used to return
ReplayEffect that is created with
Effect.thenNoReply. The command must have a field of
Effect.thenNoReply are used to explicitly mean no reply for specific commands or send reply lately.
When persisting events or recoverying from restart, commands are stashed automatcially until persistency or recovery is completed.
Sometimes you may want to stash commands to defere processing them when some conditions are meet.
Stashed messages are kept in memory and may be lost in restart or migration.