Zum Inhalt springen
Deutsch

Props

Props ist das Rezept zur Erstellung eines Actors. Nicht der Actor selbst — eine Beschreibung dessen, was zu erstellen ist, die das Framework beim Spawnen und bei jedem Restart verwendet.

Das Minimum:

import { Props } from 'actor-ts';
const props = Props.create(() => new MyActor());

Eine Factory-Funktion, die eine frische MyActor-Instanz zurückgibt. Das ist alles, was du für einen Actor mit Default-Supervision, Default-Dispatcher und der Default-unbounded-FIFO-Mailbox brauchst.

Für alles darüber hinaus verkettest du with…-Builder:

const props = Props.create(() => new Worker())
.withSupervisorStrategy(stoppingStrategy)
.withDispatcher(new MicrotaskDispatcher())
.withMailbox(() => new BoundedMailbox({ capacity: 100, overflow: 'drop-head' }));

Jedes with… gibt ein neues Props zurück — die Originale sind unveränderlich. Baue das Rezept auf und gib es dann an system.spawn oder context.spawn.

Props.create nimmt eine Factory () => new MyActor(...), nicht die Instanz direkt. Das ist wichtig, weil der Actor bei jedem Restart neu gebaut wird:

class Counter extends Actor<...> {
private count = 0;
// ...
}
// Mach das nicht:
const counter = new Counter();
const props = Props.create(() => counter); // gibt jedes Mal dieselbe Instanz zurück
// Mach das:
const props = Props.create(() => new Counter()); // frische Instanz pro Aufruf

Wenn du dieselbe Instanz bei einem Restart wiederverwendest, wird der Actor nicht zurückgesetzt — er erbt den kaputten Zustand vom fehlgeschlagenen Lauf. Die Factory-Form erzwingt einen sauberen Schiefer, was der ganze Sinn von “let it crash” ist.

Die Factory schließt jegliche benötigten Konstruktor-Argumente ein:

class Worker extends Actor<...> {
constructor(
private readonly db: ActorRef<DbMsg>,
private readonly cfg: Config,
) { super(); }
}
const props = Props.create(() => new Worker(db, cfg));

db und cfg werden vom Closure gefangen; jeder Restart sieht dieselbe Verdrahtung.

class Props<TMsg> {
static create<TMsg>(factory: () => Actor<TMsg>): Props<TMsg>;
withSupervisorStrategy(strategy: SupervisorStrategy): Props<TMsg>;
withDispatcher(dispatcher: Dispatcher): Props<TMsg>;
withMailboxCapacity(capacity: number): Props<TMsg>;
withMailbox(factory: () => Mailbox<TMsg>): Props<TMsg>;
}
import { OneForOneStrategy, Directive, stoppingStrategy } from 'actor-ts';
Props.create(() => new Worker())
.withSupervisorStrategy(stoppingStrategy);
Props.create(() => new Db())
.withSupervisorStrategy(new OneForOneStrategy(
(err) => err instanceof TransientError ? Directive.Resume : Directive.Restart,
{ maxRetries: 5, withinTimeRangeMs: 60_000 },
));

Die hier angehängte Strategie entscheidet, was passiert, wenn DIESER Actor fehlschlägt — die Strategie seines Supervisors. Im Leben jedes Actors sind zwei Strategien involviert:

  • Die auf den Props dieses Actors — angewendet auf die Fehler dieses Actors von seinem Parent.
  • Die innerhalb der Klasse dieses Actors (override supervisorStrategy) — angewendet auf die Fehler der Kinder dieses Actors.

Zwei verschiedene Dinge; die Props-Form ist für das erste. Die meisten Actors brauchen keinen Override: die Default-Strategie des Parents (oder das defaultStrategy des System-Roots) handhabt den Fehler mit einem Restart + 10/Minute-Cap. Greife zu withSupervisorStrategy, wenn das nicht passt — z.B. ein Worker, der bei Fehler gestoppt statt neu gestartet werden soll, weil sein Parent einen Ersatz spawnen wird.

Siehe Supervision für die volle Strategie-Semantik.

import { MicrotaskDispatcher, ThroughputDispatcher } from 'actor-ts';
Props.create(() => new Crunchy())
.withDispatcher(new MicrotaskDispatcher());
Props.create(() => new BulkProcessor())
.withDispatcher(new ThroughputDispatcher(100));

Überschreibe den systemweiten Dispatcher für diesen spezifischen Actor. Nützlich, wenn:

  • Ein Actor CPU-lastig ist und von Microtask-Scheduling profitiert (keine I/O-Fairness benötigt).
  • Ein Actor latenzempfindlich ist und den Default-Immediate-Dispatcher braucht, während der Rest des Systems auf einem Hochdurchsatz-Dispatcher läuft.

Für die meisten Apps gilt der System-Level-Dispatcher uniform, und du brauchst das nie. Siehe Dispatcher.

Props.create(() => new SlowConsumer()).withMailboxCapacity(500);

Kurzform für “die Default-FIFO-Mailbox, aber gebunden auf 500.” Die Default-Overflow-Policy (reject) gilt — eine volle Mailbox wirft MailboxFullError an der tell-Stelle.

Das ist der häufigste Mailbox-Override: ein einfacher Cap mit der “Sender spürt den Druck”-Semantik.

import { BoundedMailbox, PriorityMailbox } from 'actor-ts';
// Bounded + drop-head:
Props.create(() => new Telemetry())
.withMailbox(() => new BoundedMailbox({ capacity: 1_000, overflow: 'drop-head' }));
// Priority:
Props.create(() => new Worker())
.withMailbox(() => new PriorityMailbox<Msg>({
priorityFor: (m) => m.kind === 'urgent' ? 0 : 5,
}));

Volle Kontrolle — gib eine beliebige Mailbox-Subklasse zurück. Die Factory wird einmal pro Actor-Inkarnation aufgerufen; bei einem Restart wird eine frische, leere Mailbox-Datenstruktur erstellt.

Siehe Mailboxes für die Overflow-Policies und Priority-Semantik.

Der Actor-Name wird an spawn / spawn übergeben, nicht auf Props gespeichert:

const props = Props.create(() => new Worker());
system.spawn(props, 'worker-1'); // → /user/worker-1
system.spawn(props, 'worker-2'); // → /user/worker-2
this.context.spawn(props, 'sub-worker'); // → /user/<this>/sub-worker

Der Grund: Props ist als wiederverwendbar gedacht. Dasselbe Rezept kann viele Actors mit verschiedenen Namen spawnen. Wäre name auf Props, bräuchtest du ein frisches Props pro Actor — ein Footgun für Actors, die einen eindeutigen Namen pro Instanz bekommen.

Lass den Namen weg, und das Framework synthetisiert einen ('$1', '$2', …).

Für Familien von Actors mit verschiedenen Konstruktor-Args wickle Props.create in einen kleinen Helfer:

function workerProps(db: ActorRef<DbMsg>): Props<WorkerMsg> {
return Props.create(() => new Worker(db))
.withSupervisorStrategy(stoppingStrategy)
.withMailboxCapacity(500);
}
// Viele mit demselben Rezept spawnen:
for (let i = 0; i < 8; i++) {
this.context.spawn(workerProps(db), `worker-${i}`);
}

Das hält die Konfiguration an einem Ort und die Aufrufstellen sauber.

Die typed-Actor-API hat ihr eigenes Props-Äquivalent (den typed Behaviors.setup(...)-Flow); siehe Typed Actors. Das untyped Props, das hier behandelt wird, ist das allgemeinere — akzeptiert eine Factory, die Actor<TMsg> zurückgibt, wobei der Nachrichtentyp aus der Klassendeklaration des Actors selbst kommt.

  • Actor — die Basisklasse, deren Konstruktor die Factory aufruft.
  • Supervision — die Strategie, die du an withSupervisorStrategy übergibst.
  • Dispatcher — der Scheduler, den du an withDispatcher übergibst.
  • Mailboxes — die Queue, die du an withMailbox / withMailboxCapacity übergibst.
  • ActorSystem — das system.spawn(props, name?), das Props konsumiert.

Die Props-API-Referenz deckt den vollen Builder-Set ab.