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

Feature
Description

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 the AskGrain request to the caller

  • Err(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 the TellGrain .

  • 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 response

  • TellActor(actorName string, message proto.Message) error: TellActor sends a message to another actor by name without waiting for a response

  • AskGrain(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