Quickstart
Fünf Minuten von “was ist ein Actor” zu “ich habe einen laufen, der Nachrichten sendet und empfängt.” Dieser Guide setzt keine Vorerfahrung mit dem Actor-Modell voraus.
Was Du am Ende hast
Abschnitt betitelt „Was Du am Ende hast“Ein kleines Programm, das ein ActorSystem erzeugt, einen einzelnen
Greeter-Actor darin spawnt, dem Actor eine Nachricht sendet und
herunterfährt. Dieselbe Datei läuft unverändert unter Bun, Node und
Deno — das richtige Runtime-Backend wird automatisch gewählt.
Nach diesem Guide weißt Du:
- Wie Du einen Actor definierst, indem Du
Actor<T>erweiterst. - Wie Du ihn via
system.spawnAnonymous(Props.create(...))spawnst. - Den Unterschied zwischen
tell(fire-and-forget) und demonReceive-Handler des Actors. - Wie Du das System herunterfährst, ohne Timer oder Sockets zu lecken.
Installieren
Abschnitt betitelt „Installieren“bun add actor-tsnpm install actor-tsKein Installationsschritt. Direkt von npm importieren:
import { Actor, ActorSystem, Props } from 'npm:actor-ts';Mit --allow-net (für den TCP-Cluster-Transport) und
--allow-read (zum Lesen von Konfig-Dateien) ausführen, wenn Du
sie brauchst.
Für eine vollständige Installationsanleitung — einschließlich der
optionalen Peer-Dependencies (better-sqlite3, @hono/node-server,
Broker-Clients) — siehe die Installation-Seite.
Hello, Actor
Abschnitt betitelt „Hello, Actor“Erstelle hello.ts und füge ein:
import { Actor, ActorSystem, Props } from 'actor-ts';
// 1. Definiere einen Actor. Der Typparameter `<string>` schränkt// ein, welche Nachrichten dieser Actor akzeptiert — das Typsystem// fängt schlechte Sends zur Compile-Zeit.class Greeter extends Actor<string> { override onReceive(name: string): void { console.log(`hello, ${name}!`); }}
// 2. Erzeuge das System. Ein `ActorSystem` pro Prozess ist die// Norm; alles andere lebt darin.const system = ActorSystem.create('hello');
// 3. Spawne den Actor. `Props.create(() => new Greeter())` ist die// Factory; `'greeter'` ist der Name des Actors in der Hierarchie.const ref = system.spawn(Props.create(() => new Greeter()), 'greeter');
// 4. Sende eine Nachricht. `tell` ist fire-and-forget — es kehrt// sofort zurück, der Actor verarbeitet die Nachricht asynchron// auf seiner eigenen Mailbox.ref.tell('world');
// 5. Gib der Mailbox einen Tick zum Leerlaufen, dann fahre herunter.// In einer echten App würdest Du via `CoordinatedShutdown` auf// SIGTERM terminieren; für dieses Skript reicht ein kurzes// Sleep.await new Promise((resolve) => setTimeout(resolve, 20));await system.terminate();Ausführen:
bun run hello.tsnpx tsx hello.ts# oder zuerst kompilieren: tsc hello.ts && node hello.jsdeno run hello.tsDu siehst:
hello, world!Das ist die ganze Schleife: definieren → spawnen → tell → terminieren.
Was gerade passiert ist
Abschnitt betitelt „Was gerade passiert ist“Die fünf Zeilen bilden auf die fünf Kernideen des Actor-Modells ab:
-
class Greeter extends Actor<string>— ein Actor ist eine Klasse mit einer privaten Mailbox und einem einzigenonReceive-Handler. Der Typparameter sagt “dieser Actor akzeptiertstring-Nachrichten” — ihm eine Zahl zu senden, wäre ein TypeScript-Fehler zur Compile-Zeit. -
ActorSystem.create('hello')— dasActorSystemist der Runtime-Container. Es besitzt den Dispatcher (der die Nachrichtenverarbeitung plant), die Supervisor-Hierarchie (die Actor-Fehler auffängt), den Scheduler, den Event-Stream. Es gibt typischerweise einen pro Prozess. -
system.spawnAnonymous(Props.create(() => new Greeter()))—Propsist actor-ts’ Art, die Konstruktion aufzuschieben. Das System muss kontrollieren, wann die Actor-Instanz erstellt wird (auf seinem Mailbox-Thread, nicht auf Deinem), also gibst Du ihm eine Factory statt einer vorgefertigten Instanz. Das zurückgegebeneActorRefist ein Handle, nicht der Actor selbst — Du kannst es über den Cluster weitergeben, speichern, an andere Actors übergeben. -
ref.tell('world')—tellist das primäre Actor-Verb. Es queued die Nachricht in die Mailbox des Actors und kehrt sofort zurück. Der Actor verarbeitet seine Mailbox eine Nachricht nach der anderen auf einem einzigen logischen Thread, sodass Du nie über Locks oder Races innerhalb vononReceivenachdenken musst. -
system.terminate()— graceful Shutdown. Stoppt den Dispatcher, stoppt alle Actors (via den Supervisor-Tree), schließt die Transports. Gibt ein Promise zurück, das auflöst, wenn der Teardown abgeschlossen ist. Für echte Apps hänge das an einen SIGTERM-Handler — siehe Coordinated Shutdown.
Einen Schritt weiter
Abschnitt betitelt „Einen Schritt weiter“Eine string-Mailbox ist das absolute Minimum — das Framework wird
erst interessant, sobald Nachrichten Struktur tragen. Zwei kurze
Erweiterungen des Hello-Actor-Beispiels, die das konkret machen:
Typisierte Nachrichten mit Discriminated Union
Abschnitt betitelt „Typisierte Nachrichten mit Discriminated Union“Echte Actors empfangen Commands, keine nackten Werte. Die
Konvention ist eine Discriminated Union — jede Nachricht trägt ein
kind-Literal, das die Union innerhalb von onReceive verengt.
match(cmd).exhaustive() aus
ts-pattern liefert Dir
zur Compile-Zeit die Garantie, dass jede Variante behandelt ist:
füge ein neues kind ohne passenden with(...)-Arm hinzu, und
TypeScript bricht den Build ab.
import { Actor, ActorSystem, Props, type ActorRef } from 'actor-ts';import { match } from 'ts-pattern';
type Cmd = | { kind: 'inc' } | { kind: 'dec' } | { kind: 'get'; replyTo: ActorRef<number> };
class Counter extends Actor<Cmd> { private count = 0; override onReceive(cmd: Cmd): void { match(cmd) .with({ kind: 'inc' }, () => { this.count++; }) .with({ kind: 'dec' }, () => { this.count--; }) .with({ kind: 'get' }, (m) => m.replyTo.tell(this.count)) .exhaustive(); }}
const system = ActorSystem.create('counters');const counter = system.spawnAnonymous(Props.create(() => new Counter()));
counter.tell({ kind: 'inc' });counter.tell({ kind: 'inc' });counter.tell({ kind: 'dec' });Die Variante { kind: 'get'; replyTo: ActorRef<number> } ist die
Standardform für “gib mir eine Antwort”: der Anfragende übergibt
seine eigene Ref (oder, häufiger, eine von ask bereitgestellte,
siehe unten) und der Counter tellt die Antwort zurück. Für die
Tiefe siehe Messages und
Pattern Matching.
Antwort bekommen mit ask
Abschnitt betitelt „Antwort bekommen mit ask“tell ist Fire-and-Forget. Für Request/Response gibt es zwei
äquivalente Schreibweisen:
import { ActorSystem, Props } from 'actor-ts';
const system = ActorSystem.create('counters');const counter = system.spawnAnonymous(Props.create(() => new Counter()));
counter.tell({ kind: 'inc' });counter.tell({ kind: 'inc' });
// Methode-auf-Ref — knapp, leitet den Reply-Typ ab:const value = await counter.ask<number>({ kind: 'get' }, 5_000);console.log(value); // 2
await system.terminate();Oder mit der freien Funktion für denselben Effekt:
import { ask } from 'actor-ts';
const value = await ask<Cmd, number>(counter, { kind: 'get' }, 5_000);In beiden Fällen spawnt das Framework unter der Haube einen
One-Shot-Reply-Actor, setzt ihn als replyTo UND als
context.sender beim Ziel ein und löst das Promise mit der
ersten Antwort auf (oder rejected mit AskTimeoutError). Aufrufer
schreiben selbst nie replyTo hin.
Siehe Ask Pattern für Timeout-Verhalten, Error Handling und den Dead-Letter-Fall (Ziel-Actor gestoppt, bevor er geantwortet hat).
Wie es weitergeht
Abschnitt betitelt „Wie es weitergeht“Du hast einen laufenden Actor. Von hier aus verzweigt sich die Doku, je nachdem, was Du tun willst:
-
Interessantere Nachrichten senden: siehe Messages für die Konventionen (discriminated unions, Immutability, das
kind-Feld) und Pattern Matching für dasmatch().exhaustive()-Dispatch-Idiom, das diese Codebase überall verwendet. -
Antwort vom Actor zurückbekommen: siehe Ask Pattern — das Request/Response-Äquivalent zu
tell. -
Fehler behandeln: siehe Supervision dafür, wie Eltern-Actors sich von Kind-Abstürzen erholen.
-
Über Maschinen verteilen: siehe die Cluster Overview und Sharding Overview — derselbe Code, den Du oben geschrieben hast, kann über N Nodes mit einer einzigen zusätzlichen Zeile laufen.
Cluster.bootstrapbaut dasActorSystem, tritt dem Cluster bei, startet den Receptionist und verdrahtetSIGTERM/SIGINT-Shutdown in einem Call:import { Cluster, Props } from 'actor-ts';const { system } = await Cluster.bootstrap({ name: 'hello' });const ref = system.spawn(Props.create(() => new Greeter()), 'greeter');ref.tell('world');Die Discovery defaultet auf eine env-gesteuerte Kette —
CLUSTER_SEEDS→ Kubernetes API → DNS — sodass derselbe Code lokal als Single-Node läuft und in Produktion ohne Config-Änderung einem bestehenden Cluster beitritt. -
State über Neustarts hinweg persistieren: siehe PersistentActor für Event Sourcing und Snapshots für begrenzte Recovery-Zeit.
Wenn Du die ganze Oberfläche des Frameworks auf einmal sehen willst, ist das Chat-Beispiel die umfassendste End-to-End-Demonstration — es verwendet Sharding, Persistenz, Distributed PubSub, Distributed Data, Cluster Singleton, HTTP-Routing, sechs austauschbare Frontends, alles.