Grain
Origin and Philosophy
The Grain abstraction in GoAkt is inspired by the Virtual Actor Model from Microsoft Orleans. However, GoAkt adapts this concept to Go’s concurrency model with minimalistic and composable design principles.
Grains in GoAkt are location-transparent, message-driven entities with unique identities. Unlike traditional actors, Grains in GoAkt have no internal lifecycle logic—they're managed entirely by the actor system.
What is Grain in GoAkt?
A Grain is a specialised actor that:
Has a globally unique identity
Is location-transparent across local and clustered deployments
Can be stateful or stateless
Automatically activated when needed
Automatically removed from memory when inactive after a period of time.
Is lightweight and consumes no resources when inactive
Lives as long as the actor system unless explicitly deactivated
Can communicate with other Grains or Actors
Characteristics
Unique Identity
Every Grain has a consistent, system-wide identity that is used to communicate with the Grain
Stateless or Stateful
Stateless by default; statefulness is implemented via Extensions
No Lifecycle API
No Spawn
or Shutdown
like actors; system manages creation and shutdown
Automatic Activation
Grains are automatically activated when requesting their identity.
Activation/Deactivation
OnActivate
and OnDeactivate
hooks for setup and teardown
Inactive Resource Consumption
Inactive Grains consume no CPU or memory
Automatic Deactivation
Grains are automatically deactivated and removed from memory when idle after a certain period of time that can be configured. The default is two minutes
Explicit Deactivation
Send PoisonPill
to explicitly deactivate a Grain
Cluster-Aware Relocation
Grains are automatically redeployed on other nodes (unless disabled)
Message-Passing Model
Communicate using Tell
(fire-and-forget) or Ask
(request/response)
Actor Interoperability
Grains and Actors can message each other seamlessly
Get Started
Grain interface
To define a Grain one needs to implement the Grain interface:
type Grain interface {
// OnActivate is called when the grain is loaded into memory.
// Use this to initialize state or resources.
//
// Arguments:
// - ctx: context for cancellation and deadlines.
// - props: grain properties and system-level references.
// Returns:
// - error: non-nil to indicate activation failure (grain will not be activated).
OnActivate(ctx context.Context, props *GrainProps) error
// OnReceive is called when the grain receives a message.
//
// Arguments:
// - ctx: GrainContext containing the message, sender, grain identity, and system references.
// Behavior:
// - Processes the message and updates grain state as needed.
// - Always respect cancellation and deadlines via the context in GrainContext.
// - Do not retain references to the GrainContext or its fields beyond the method scope.
OnReceive(ctx *GrainContext)
// OnDeactivate is called before the grain is removed from memory.
// Use this to persist state and release resources.
//
// Arguments:
// - ctx: context for cancellation and deadlines.
// - props: grain properties and system-level references.
// Returns:
// - error: non-nil to indicate deactivation failure (system may log or handle the failure).
OnDeactivate(ctx context.Context, props *GrainProps) error
}
Grain Identity
Once the Grain interface is implemented, you will need to acquire or create an identity for each instance of the given Grain using the ActorSystem
method GrainIdentity
.
Example:
grain := &Grain{}
accountID := uuid.NewString()
identity, err := actorSystem.GrainIdentity(ctx, accountID, func(ctx context.Context) (goakt.Grain, error) {
return grain, nil
})
Messaging
Once the grain identity is obtained then you can start messaging the Grain using the following ActorSystem
methods:
AskGrain
: Send a synchronous request message to the Grain and expects a response or an error. The request is time-bound.TellGrain
: sends an asynchronous message to a Grain (virtual actor) identified by the given identity.
Message Handling
Every request sent to a Grain is time-bound.
In the OnReceive
message handler of a Grain one can given the GrainContext
perform the following actions:
Response(resp proto.Message)
: This method helps send a response to theAskGrain
request to the callerErr(err error)
: To return an error from the handler. This is the recommended way instead of panicking inside the message handler. Even if the message handler does panic, GoAkt will recover smoothly without crashing the application.NoErr()
: This method is used to state that the message handler return without any error. Use this method when dealing with theTellGrain
.Unhandled()
: Unhandled marks the currently received message as unhandled by the Grain.AskActor(name string, message proto.Message, timeout time.Duration) (proto.Message, error)
: AskActor sends a message to another actor by name and waits for a responseTellActor(actorName string, message proto.Message) error
: TellActor sends a message to another actor by name without waiting for a responseAskGrain(to *GrainIdentity, message proto.Message, timeout time.Duration) (proto.Message, error)
: AskGrain sends a message to another Grain and waits for a response.TellGrain(to *GrainIdentity, message proto.Message) error
: TellGrain sends a message to another Grain without waiting for a response.GrainIdentity(name string, factory GrainFactory, opts ...GrainOption) (*GrainIdentity, error)
: GrainIdentity creates or retrieves a unique identity for a Grain instance.Self() *GrainIdentity
: Self returns the unique identifier of the Grain instance.Message() proto.Message
: Message returns the message currently being processed by the Grain.Context() context.Context
: Context returns the underlying context associated with the GrainContext.ActorSystem() ActorSystem
: ActorSystem returns the ActorSystem that manages the Grain.
Statefulness
To make a Grain stateful:
Use the Extension capability to attach persistence storage.
Extensions can implement snapshotting, durable storage, etc.
This keeps the core Grain logic clean and testable.
Example
Kindly check the following links:
Last updated