Spawning actors
このコンテンツはまだ日本語訳がありません。
Spawning takes two things: what to build, and — only when the defaults are wrong — how to run it.
import { ActorSystem } from 'actor-ts';
const system = ActorSystem.create('hello');
system.spawn(Greeter, 'greeter'); // the class itselfsystem.spawn(() => new Worker(database), 'worker-1'); // a factoryThat is the whole common case. Defaults are an unbounded FIFO mailbox, the system dispatcher, and the parent’s supervision.
The class form and the factory form
Section titled “The class form and the factory form”Pass the class when its constructor takes no arguments. Nothing else is needed — the framework constructs a fresh instance itself.
class Greeter extends Actor<string> { override onReceive(name: string): void { console.log(`hello ${name}`); }}
system.spawn(Greeter, 'greeter');Pass a factory when the actor needs dependencies. The closure captures them, and every restart sees the same wiring:
class Worker extends Actor<WorkerMessage> { constructor(private readonly database: ActorRef<DatabaseMessage>) { super(); }}
system.spawn(() => new Worker(database), 'worker-1');A class whose constructor does take arguments is rejected at the
call site — passing it alone could only construct it with undefined
dependencies:
system.spawn(Worker, 'worker-1');// ✗ Worker needs a constructor argument, so the class alone is not// enough to build it — pass a factory that supplies it:// () => new Worker(database).Why a factory, and never an instance
Section titled “Why a factory, and never an instance”Neither form lets you hand over an actor instance, and that is deliberate: the actor is rebuilt on every restart.
// Rejected — an instance is neither a class nor a factory:const counter = new Counter();system.spawn(counter, 'counter');
// Fine, and fresh on every restart:system.spawn(Counter, 'counter');Reusing one instance across a restart means the actor never resets — it inherits the broken state from the failed run, which is the opposite of what “let it crash” is for.
Naming
Section titled “Naming”The name is passed to spawn, never attached to the actor:
system.spawn(Greeter, 'greeter-1'); // → /user/greeter-1system.spawn(Greeter, 'greeter-2'); // → /user/greeter-2this.context.spawn(Greeter, 'sub-greeter'); // → /user/<this>/sub-greeterNames must be unique among siblings. For an actor whose path does not
matter — one-shot async work, throwaway helpers — use
spawnAnonymous, which synthesizes $anonymous-<n>-<random>:
const ref = system.spawnAnonymous(Counter);ActorOptions
Section titled “ActorOptions”Everything beyond the defaults goes in a third argument. Build it with the fluent builder:
import { ActorOptions, stoppingStrategy, MicrotaskDispatcher, BoundedMailbox } from 'actor-ts';
const workerOptions = ActorOptions.create<WorkerMessage>() .withSupervisorStrategy(stoppingStrategy) .withDispatcher(new MicrotaskDispatcher()) .withMailbox(() => new BoundedMailbox({ capacity: 100, overflow: 'drop-head' }));
system.spawn(() => new Worker(database), 'worker-1', workerOptions);A plain object is the shorthand alternative and reads identically:
system.spawn(() => new Worker(database), 'worker-1', { mailboxCapacity: 500 });class ActorOptionsBuilder<TMessage> { static create<TMessage>(): ActorOptionsBuilder<TMessage>;
withSupervisorStrategy(strategy: SupervisorStrategy): this; withDispatcher(dispatcher: Dispatcher): this; withMailboxCapacity(capacity: number): this; withMailbox(factory: () => Mailbox<TMessage>): this; withInternal(internal?: boolean): this; withDisplayName(displayName: string): this; withEntity(entity: EntityContext): this;}withSupervisorStrategy
Section titled “withSupervisorStrategy”import { ActorOptions, OneForOneStrategy, Directive, stoppingStrategy } from 'actor-ts';
const workerOptions = ActorOptions.create().withSupervisorStrategy(stoppingStrategy);
const databaseOptions = ActorOptions.create() .withSupervisorStrategy(new OneForOneStrategy( (error) => error instanceof TransientError ? Directive.Resume : Directive.Restart, { maxRetries: 5, withinTimeRangeMs: 60_000 }, ));The strategy set here decides what happens when THIS actor fails. Two strategies are involved in every actor’s life:
- The one in this actor’s spawn options — applied to this actor’s failures, by its parent.
- The one inside this actor’s class (
override supervisorStrategy) — applied to this actor’s children’s failures.
Most actors need neither: the parent’s default (or the system root’s
defaultStrategy) restarts with a 10-per-minute cap. Reach for this
when that is wrong — e.g. a worker that should be stopped rather than
restarted because its parent will spawn a replacement.
The parent resolves the strategy in this order, first match wins:
- The failing child’s own spawn options.
- The parent actor’s
supervisorStrategy()override. defaultStrategy.
So a single child can opt out of its parent’s policy without affecting
its siblings. Two details fall out of the strategy being applied by
the parent: an all-for-one strategy set on a child still widens to
every sibling, and the restart budget (maxRetries /
withinTimeRangeMs) is counted per parent, so siblings share one
allowance.
See Supervision for the full semantics.
withDispatcher
Section titled “withDispatcher”import { ActorOptions, MicrotaskDispatcher, ThroughputDispatcher } from 'actor-ts';
const crunchyOptions = ActorOptions.create().withDispatcher(new MicrotaskDispatcher());const bulkOptions = ActorOptions.create().withDispatcher(new ThroughputDispatcher(100));Override the system-wide dispatcher for one actor. Useful when a single actor is CPU-heavy and benefits from microtask scheduling, or is latency-sensitive while the rest of the system runs on a high-throughput dispatcher. For most apps the system-level dispatcher applies uniformly and you never need this — see Dispatchers.
withMailboxCapacity
Section titled “withMailboxCapacity”import { ActorOptions } from 'actor-ts';
const consumerOptions = ActorOptions.create().withMailboxCapacity(500);Bounds the mailbox, which is otherwise unbounded — so this is the call
that introduces message loss. drop-head applies unless you say
otherwise: a full mailbox discards the oldest queued message to make
room, without throwing. Add withMailboxOverflow('drop-new') to
discard the arriving message instead, or 'reject' to throw
MailboxFullError at the tell site so the sender feels the pressure.
withMailbox
Section titled “withMailbox”import { ActorOptions, BoundedMailbox, PriorityMailbox } from 'actor-ts';
const telemetryOptions = ActorOptions.create() .withMailbox(() => new BoundedMailbox({ capacity: 1_000, overflow: 'drop-head' }));
const workerOptions = ActorOptions.create<Message>() .withMailbox(() => new PriorityMailbox<Message>({ priorityFor: (message) => message.kind === 'urgent' ? 0 : 5, }));Full control — return any Mailbox subclass. The factory is called
once, when the actor’s cell is constructed; the mailbox then
outlives every restart. On restart only the Actor instance is
rebuilt — the same mailbox is resumed in place, so messages queued
before the crash are still there.
See Mailboxes for the overflow policies and priority semantics.
withInternal
Section titled “withInternal”Marks an actor as belonging to tooling rather than to the application, and its children with it. Whole-system instrumentation skips it: without the mark, DevTools’ own hub — which publishes the spans it has just recorded — traces itself, and every batch comes back as the payload of the next one.
import { ActorOptions } from 'actor-ts';
const probeOptions = ActorOptions.create().withInternal();
system.spawn(MyProbe, 'my-probe', probeOptions);You need it only when writing a tool that observes the system it runs in. Application actors should not be marked: hiding real work from the profiler is how a performance problem stays invisible.
withDisplayName
Section titled “withDisplayName”Names the actor in log lines and in the DevTools actor tree. The spawn-site
counterpart to overriding Actor.displayName() — reach for it when the actor
has no subclass of yours to override: a Behaviors actor, a sharded entity,
a singleton.
const workerOptions = ActorOptions.create().withDisplayName('ingest-worker');
system.spawn(IngestWorker, 'ingest', workerOptions);Outranks the method, exactly as withSupervisorStrategy outranks
Actor.supervisorStrategy(); context.setDisplayName(...) at runtime
outranks both. Purely cosmetic — the path stays the identity everywhere that
routes or correlates, so metric labels, tracing attributes and every
cluster-wire identifier are unaffected.
See Logging for how the name reaches a log record.
withEntity
Section titled “withEntity”Gives an actor a sharding identity, readable back off this.entityId /
this.context.entity. ClusterSharding sets this itself for every
entity a shard creates; it is public for the test bench, where an
entity that derives its persistenceId from this.entityId would
otherwise be unspawnable without a cluster standing behind it.
import { ActorOptions } from 'actor-ts';
const cartOptions = ActorOptions.create() .withEntity({ entityId: 'user-42', typeName: 'cart', shardId: 3 });
const cart = system.spawn(CartEntity, 'cart-under-test', cartOptions);Common patterns
Section titled “Common patterns”A helper for a family of actors
Section titled “A helper for a family of actors”import { ActorOptions, type ActorFactory } from 'actor-ts';
const workerOptions = ActorOptions.create<WorkerMessage>() .withSupervisorStrategy(stoppingStrategy) .withMailboxCapacity(500);
const worker = (database: ActorRef<DatabaseMessage>): ActorFactory<WorkerMessage> => () => new Worker(database);
for (let i = 0; i < 8; i++) { this.context.spawn(worker(database), `worker-${i}`, workerOptions);}The options object is reusable across every actor spawned from it — it is snapshotted per spawn, so the eight workers do not share state.
Typed actors
Section titled “Typed actors”The typed API spawns a Behavior instead of a class or factory:
const ref = system.spawnTyped(counter(0), 'counter');const anonymous = system.spawnTypedAnonymous(counter(0));To hand a Behavior to an API that takes an actor — Router,
ClusterSharding.start — wrap it with typedActor(behavior). See
Typed actors.
Where to next
Section titled “Where to next”- Actor — the base class whose constructor is invoked.
- Supervision — the strategy you pass
to
withSupervisorStrategy. - Dispatchers — the scheduler you
pass to
withDispatcher. - Mailboxes — the queue you pass to
withMailbox/withMailboxCapacity. - ActorSystem — the
system.spawn(actor, name, options?)that consumes all of this.
