ActorSystem
Das ActorSystem ist der Top-Level-Container für Actors. Eines
pro logischer Anwendung — manchmal eines pro Prozess, manchmal ein
paar, die nebeneinander laufen (z.B. ein Worker-Thread-Isolations-Setup).
Jeder Actor lebt innerhalb eines Systems; das System besitzt den
Dispatcher (der die Nachrichtenverarbeitung plant), den Scheduler (der
Timer ausführt), den Supervisionsbaum (der Actor-Fehler abfängt), den
Event-Stream und alle Extensions, die du registriert hast.
Eines erstellen
Abschnitt betitelt „Eines erstellen“import { ActorSystem } from 'actor-ts';
const system = ActorSystem.create('my-app');Der String ist der Name des Systems — er erscheint in
Actor-Pfaden (actor-ts://my-app/user/...), Log-Zeilen und der
Cluster-Identifikation. Verschiedene Systeme können mit
verschiedenen Namen koexistieren; derselbe Name in einem Cluster-Setup
bedeutet “ich trete dem bestehenden Cluster bei”, ein anderer Name
bedeutet “ich bin ein separater Cluster”.
create kehrt synchron zurück. Die Root-Guardians des Systems werden
eifrig erzeugt; User-Actors existieren noch nicht — du spawnst sie
über spawn (unten beschrieben).
Konfiguration
Abschnitt betitelt „Konfiguration“ActorSystem.create nimmt ein optionales Settings-Objekt als zweites
Argument:
const system = ActorSystem.create('my-app', { logLevel: 'info', configFile: './application.conf',});Die vollständige Settings-Form:
| Feld | Zweck |
|---|---|
logger | Eigene Logger-Instanz. Standardmäßig ein Console-Logger, der logLevel respektiert. |
logLevel | Einer von debug / info / warn / error / silent. |
dispatcher | Eigener Dispatcher. Standardmäßig ein Microtask-basierter Dispatcher; Tests tauschen typischerweise einen Immediate- oder Manual-Dispatcher ein. |
scheduler | Eigener Scheduler. Standardmäßig ein Echtzeit-Scheduler; Tests injizieren ManualScheduler, um die Zeit zu kontrollieren. |
config | Entweder eine vorgefertigte Config oder ein einfaches Objekt mit HOCON-Overrides. Wird über die Referenz-Defaults + einer eventuellen application.conf gelegt. |
configFile | Expliziter Pfad zu einer application.conf-Datei. Überschreibt die ACTOR_TS_CONFIG-Env-Variable und das CWD-Lookup. |
Konstruktor-Settings gewinnen immer gegenüber allem in der Config — sie sind die expliziten Code-Level-Overrides.
HOCON-Config-Dateien
Abschnitt betitelt „HOCON-Config-Dateien“Für größere Anwendungen bevorzuge eine application.conf-Datei im
Projekt-Root:
actor-ts { log-level = "info" dispatcher { throughput = 100 } cluster { gossip-interval = 500ms failure-detector.unreachable-after = 1500ms }}Das Framework lädt sie automatisch, wenn vorhanden. ENV-Substitution
(${?ENV_NAME}) funktioniert wie in der HOCON-Spec definiert — aus
der Umgebung gezogene Werte fallen auf den Default zurück, wenn sie
nicht gesetzt sind. Siehe Konfiguration
für jeden Schlüssel, den das Framework liest.
Actors spawnen
Abschnitt betitelt „Actors spawnen“Top-Level-Actors werden über system.spawn gespawnt:
import { Props } from 'actor-ts';
const root = system.spawn( Props.create(() => new MyRootActor()), 'root', // optionaler Name; Framework wählt einen, wenn weggelassen);Die zurückgegebene ActorRef ist ein Handle, keine Instanz. Gib es
weiter, speichere es, übergib es an andere Actors.
Innerhalb eines Actors werden Child-Actors über context.spawn
gespawnt, nicht über system.spawn:
class Parent extends Actor<...> { override onReceive(msg) { const child = this.context.spawn( Props.create(() => new Child()), 'worker', ); }}Kinder sind an den Lebenszyklus des Parents gebunden — wenn der
Parent stoppt, stoppen zuerst alle Kinder. Fehler von Kindern
eskalieren an die Supervisor-Strategie
des Parents. Top-Level-Actors (aus system.spawn) eskalieren
stattdessen an den Root-Guardian des Systems.
Die Guardian-Hierarchie
Abschnitt betitelt „Die Guardian-Hierarchie“Jeder Actor hat einen Pfad unter dem System-Root. Drei “Guardian”-Top-Level-Actors sitzen direkt unter dem Root:
Wenn du system.spawnAnonymous(props) aufrufst, wird der Actor unter
/user erzeugt. Wenn das System terminiert, stoppen die Guardians in
umgekehrter Reihenfolge nacheinander: User-Actors zuerst (damit sie
ihre Arbeit beenden können), dann die System-Internas.
Der /deadLetters-”Actor” ist speziell — Nachrichten an ein tell
auf einer gestoppten Ref oder an eine nie existierte Ref werden
dorthin geleitet. Standardmäßig loggt das System Dead Letters auf
debug-Level; abonniere den Event-Stream, wenn du programmatisch
reagieren willst.
Extensions
Abschnitt betitelt „Extensions“Extensions sind das Plugin-System des Frameworks. Cluster,
Persistenz, DistributedData, DistributedPubSub, HTTP — sie sind alle
Extensions. Du registrierst sie einmal auf System-Ebene und erreichst
sie dann über system.extension(...):
import { Cluster, DistributedDataId } from 'actor-ts';
const cluster = await Cluster.join(system, { /* ... */ });const dd = system.extension(DistributedDataId).start(cluster);Extensions sind lazy: sie initialisieren sich nicht, bis du nach
ihnen greifst. Eine App, die nie
system.extension(DistributedDataId) aufruft, startet nie einen
DD-Replicator. Das hält Single-Process-Apps klein; übernimm Features,
indem du nach ihnen greifst, lass sie weg, indem du es nicht tust.
Eine eigene Extension schreiben
Abschnitt betitelt „Eine eigene Extension schreiben“import { type Extension, type ExtensionId } from 'actor-ts';
class MetricsCollector implements Extension { constructor(private readonly system: ActorSystem) {} incCounter(name: string): void { /* ... */ }}
const MetricsCollectorId: ExtensionId<MetricsCollector> = { name: 'MetricsCollector', create: (system) => new MetricsCollector(system),};
// Lookup ist idempotent — der erste Aufruf erzeugt, folgende Aufrufe// geben die gecachte Instanz zurück.const metrics = system.extension(MetricsCollectorId);metrics.incCounter('login.success');Extensions sind nützlich, wenn:
- Du übergreifenden Zustand brauchst, der von vielen Actors geteilt wird (ein Connection-Pool, ein Metrics-Collector).
- Der Zustand teuer zu initialisieren ist und nicht existieren sollte, wenn niemand danach greift (ein Cluster-Join, ein DD-Replicator).
- Du eine saubere Möglichkeit willst, Test-Doubles in Unit-Tests zu
injizieren (überschreibe den
ExtensionId-Resolver).
Terminieren
Abschnitt betitelt „Terminieren“await system.terminate();terminate führt einen geordneten Shutdown durch:
- Cluster benachrichtigen (falls beigetreten) — “ich verlasse” gossipen, damit Peers nicht mehr zu diesem Node routen.
/userrekursiv stoppen — deine Actors bekommenpostStop, Kinder zuerst. Actors mit laufenden asynconReceives beenden ihre aktuelle Nachricht, bevor sie stoppen./systemstoppen — Framework-Internas wickeln sich ab.- Dispatcher und Scheduler schließen — keine neuen Nachrichten, keine neuen Timer.
- Das zurückgegebene Promise resolven.
Für Produktions-Apps wickelst du das typischerweise in einen SIGTERM-Handler:
process.on('SIGTERM', async () => { await system.terminate(); process.exit(0);});…aber das Framework bietet ein reicheres Pattern dafür — siehe Coordinated Shutdown für das 12-phasige Ordered-Shutdown-DSL, das K8s-PreStop-Hooks, laufende HTTP-Requests, das Drainen von Brokern usw. handhabt.
Wie viele Systeme pro Prozess?
Abschnitt betitelt „Wie viele Systeme pro Prozess?“Die übliche Antwort ist eins. Ein zweites System im selben Prozess bedeutet einen separaten Cluster, einen separaten Dispatcher, einen separaten Supervisionsbaum — typischerweise mehr Overhead, als der Use Case rechtfertigt.
Zwei Situationen, in denen ein zweites System Sinn macht:
- Worker-Thread-Isolation: der Hauptthread läuft mit einem
System, ein Worker-Thread mit einem anderen, beide spannen
denselben Cluster über den
MessageChannelTransportauf. Das ist das Worker-Mesh-Pattern — mehrere Systeme pro OS-Prozess, alle Teil desselben Clusters. - Test-Fixtures: ein
TestActorSystempro Testfall, damit das Cleanup garantiert ist. Siehe TestKit.
Häufige Fallstricke
Abschnitt betitelt „Häufige Fallstricke“Wie es weitergeht
Abschnitt betitelt „Wie es weitergeht“- Actor — die Klasse, die du in das System spawnst.
- Coordinated Shutdown —
Graceful-Shutdown-DSL jenseits eines einfachen
terminate. - Cluster-Überblick — wenn du von einem System pro Prozess zu vielen Systemen in einem Cluster gehst.
- Konfiguration — jeder HOCON-Schlüssel, den das Framework liest, gruppiert nach Extension.
Die ActorSystem-Klassen-API-Referenz
dokumentiert jede hier diskutierte öffentliche Methode.