Skip to main content

Concept

Grains are virtual actors: identity-addressed and managed by the framework. They are activated on first message and deactivated when idle. You don’t spawn them explicitly; you send to an identity and the framework routes to the right instance.

The Grain interface

Every grain must implement the Grain interface:

GrainContext

Inside OnReceive, GrainContext provides:

Identity and messaging

Use the generic actor.GrainOf function to obtain an identity. The type parameter must be a pointer to your grain struct; the framework derives the grain kind from it, so no factory is required. Then use TellGrain or AskGrain with that identity. The instance name (e.g., "user-123") uniquely identifies the grain within its kind.
When activation is needed, the framework constructs the grain as a zero value of its struct type. Put initialization logic in OnActivate and supply external resources (database handles, clients, configuration) through WithGrainDependencies; they are available from the GrainProps passed to OnActivate. This is the same construction contract the cluster applies when it recreates or relocates a grain on another node.
Calling GrainOf automatically registers the grain kind on the calling node. In cluster mode, nodes that can host a grain without ever resolving it themselves (for example, targets of relocation or remote activation) must register the kind at startup, either with RegisterGrainKind or through ClusterConfig.WithGrains.
The factory-based GrainIdentity(ctx, name, factory, opts...) API is deprecated. It still works, but factories that capture dependencies in their closure only take effect on the local node; a grain recreated or relocated elsewhere is always built as a zero value. Migrate to GrainOf with OnActivate-based initialization.

When to use grains

  • Entity-per-identity patterns (users, sessions, devices)
  • Large populations that are mostly idle
  • When you want the framework to manage lifecycle and placement

When to use actors

  • Long-lived services
  • Explicit lifecycle control
  • Infrastructure components

Grain lifecycle

  1. First message arrives → framework activates the grain (calls OnActivate)
  2. Messages are routed to the grain’s OnReceive
  3. After idle timeout (passivation) → grain is deactivated (OnDeactivate)
  4. Next message → grain is reactivated

Activation guarantee

In cluster mode, the system claims grain ownership in the cluster registry using an atomic put-if-absent operation before activating locally. If another node already owns the grain, requests are forwarded to that owner. If local activation fails after a successful claim, the claim is removed. When configured, the grain activation barrier delays grain activations until the cluster reaches the minimum peers quorum (or until the barrier timeout elapses).

See also

  • Passivation — Grains use passivation for idle-based deactivation
  • Actor Model — When to use actors vs grains