Class KeyValueEntity<S>
- Type Parameters:
S
- The type of the state for this entity
Key Value Entities provide strong consistency guarantees through entity sharding, where each entity instance is identified by a unique id and distributed across the service cluster. Only one instance of each entity exists in the cluster at any time, ensuring sequential message processing without concurrency concerns.
The entity state is kept in memory while active and can serve read requests or command validation without additional reads from durable storage. Inactive entities are passivated and recover their state from durable storage when accessed again.
Implementation Steps
- Define the entity's state
- Implement behavior in command handlers
Command Handlers
Command handlers are methods that return anKeyValueEntity.Effect
and define how the entity responds to commands.
The Effect API allows you to:
- Update the entity state and send a reply to the caller
- Reply directly without state changes
- Return an error message
- Delete the entity
Example Implementation
@ComponentId("counter")
public class CounterEntity extends KeyValueEntity<Counter> {
public CounterEntity(KeyValueEntityContext context) {
// Constructor can accept KeyValueEntityContext and custom dependency types
}
@Override
public Counter emptyState() {
return new Counter(0);
}
public Effect<Counter> set(int value) {
Counter newCounter = new Counter(value);
return effects()
.updateState(newCounter)
.thenReply(newCounter);
}
public ReadOnlyEffect<Counter> get() {
return effects().reply(currentState());
}
}
Concrete classes can accept the following types in the constructor:
KeyValueEntityContext
- provides entity context information- Custom types provided by a
DependencyProvider
from the service setup
Concrete classes must be annotated with ComponentId
with a
stable, unique identifier that cannot be changed after production deployment.
Multi-region Replication
Key Value Entities support multi-region replication for resilience and performance. Write requests are handled by the primary region, while read requests can be served from any region. UseKeyValueEntity.ReadOnlyEffect
for read-only operations that can be served from replicas.
Immutable state record
It is recommended to use immutable state objects, such as Java records, for the entity state. Immutable state ensures thread safety and prevents accidental modifications that could lead to inconsistent state or concurrency issues.
While mutable state classes are supported, they require careful handling:
- Mutable state should not be shared outside the entity
- Mutable state should not be passed to other threads, such as in
CompletionStage
operations - Any modifications to mutable state must be done within the entity's command handlers
Collections in State: Collections (such as List
, Set
, Map
) are
typically mutable even when contained within immutable objects. When updating state that contains collections,
you should create copies of the collections rather than modifying them in place. This ensures that the
previous state remains unchanged and prevents unintended side effects.
Performance Considerations: Defensive copying of collections can introduce performance overhead, especially for large collections or frequent updates. In performance-critical scenarios, this recommendation can be carefully tuned by using mutable state with strict adherence to the safety guidelines mentioned above.
Using immutable records with defensive copying of collections eliminates concurrency concerns and is the preferred approach for state modeling in most cases.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interface
An Effect describes the actions that the Akka runtime should perform after a command handler completes.static interface
A read-only effect that does not modify the entity state. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected final CommandContext
Provides access to additional context and metadata for the current command being processed.protected final S
Returns the current state of this entity as stored in memory.protected final KeyValueEntity.Effect.Builder
<S> effects()
Returns the initial empty state object for this entity.protected boolean
Returns whether this entity has been marked for deletion.
-
Constructor Details
-
KeyValueEntity
public KeyValueEntity()
-
-
Method Details
-
emptyState
Returns the initial empty state object for this entity. This state is used when the entity is first created and before any commands have been processed.Also known as "zero state" or "neutral state". This method is called when the entity is instantiated for the first time or when recovering from storage without any persisted state.
The default implementation returns
null
. Override this method to provide a more meaningful initial state for your entity.- Returns:
- the initial state object, or
null
if no initial state is defined
-
commandContext
Provides access to additional context and metadata for the current command being processed. This includes information such as the command name, entity id, and tracing context.This method can only be called from within a command handler method. Attempting to access it from the constructor or outside of command processing will result in an exception.
- Returns:
- the command context for the current command
- Throws:
IllegalStateException
- if accessed outside a command handler method
-
currentState
Returns the current state of this entity as stored in memory. This represents the latest persisted state and any updates made during the current command processing.Important: Modifying the returned state object directly will not persist the changes. To save state changes, you must use
effects().updateState(newState)
in your command handler's return value.This method can only be called from within a command handler method. Attempting to access it from the constructor or outside of command processing will result in an exception.
- Returns:
- the current state of the entity, which may initially be
null
ifemptyState()
has not been defined - Throws:
IllegalStateException
- if accessed outside a command handler method
-
isDeleted
protected boolean isDeleted()Returns whether this entity has been marked for deletion. When an entity is deleted usingeffects().deleteEntity()
, it will still exist with an empty state for some time before being completely removed.After deletion, the entity can still handle read requests but will have an empty state. No further state changes are allowed after deletion. The entity will be completely cleaned up after a default period of one week.
- Returns:
true
if the entity has been deleted,false
otherwise
-
effects
-