Actor-Pfade
Jeder Actor im System hat einen Pfad — eine hierarchische Kennung, gebaut aus seiner Position im Supervisionsbaum:
actor-ts://my-app/user/api/sessions/user-42Von links nach rechts gelesen:
| Segment | Was es ist |
|---|---|
actor-ts:// | Das Schema — für jeden actor-ts-Pfad gleich. |
my-app | Der Systemname aus ActorSystem.create('my-app'). |
user | Der User-Guardian — jeder Actor, den du spawnst, lebt unter /user. |
api | Ein Urgroßelter (z.B. ein Api-Manager). |
sessions | Der direkte Parent (z.B. ein SessionManager). |
user-42 | Der eigene Name des Leaf-Actors. |
Der Pfad identifiziert einen Actor eindeutig innerhalb seines Systems, wie ein Dateisystempfad eine Datei identifiziert: zu jedem Zeitpunkt löst der Pfad entweder zu einem lebenden Actor auf oder nicht.
Du siehst Pfade an drei Stellen:
- Log-Zeilen — das
logjedes Actors ist an seinen Pfad gebunden, Log-Nachrichten tragen also die vollständige Hierarchie. Terminated.actor.path— wenn ein Actor stoppt, können Watcher den Pfad inspizieren, um auf den jeweiligen Actor zu dispatchen.ActorSelection— Actors per Pfad-String nachschlagen, statt eine Ref zu halten.
Der ActorPath-Wert
Abschnitt betitelt „Der ActorPath-Wert“context.path und ref.path geben beide einen ActorPath zurück:
import { Actor } from 'actor-ts';
class Logger extends Actor<Message> { override preStart(): void { this.log.info(`starting; my path is ${this.context.path}`); // → "starting; my path is actor-ts://my-app/user/logger"
this.log.info(`depth = ${this.context.path.depth()}`); // → "depth = 2" (Root ist 0, /user ist 1, /user/logger ist 2)
this.log.info(`elements = ${this.context.path.elements().join(' → ')}`); // → "elements = → user → logger" (führende Lücke = die leer benannte Wurzel) }}Die vollständige Schnittstelle:
| Member | Was es dir gibt |
|---|---|
path.name | Das Leaf-Segment (oben 'logger'). |
path.parent | Der ActorPath des Parents oder null für die Wurzel. |
path.systemName | Der Systemname (oben 'my-app'). |
path.elements() | Array der Segmentnamen von der Wurzel zum Leaf. |
path.depth() | Abstand zur Wurzel (Wurzel = 0). |
path.isAncestorOf(other) | Schließt dieser Pfad other ein? |
path.equals(other) | Lexikalische Gleichheit (vergleicht toString()-Output). |
path.toString() | Kanonische URI-Form: actor-ts://<sys>/<segments>. |
Pfade sind unveränderliche Werte — path.child('thing') gibt
einen neuen Pfad zurück; es mutiert das Original nicht.
Wie Pfade zugewiesen werden
Abschnitt betitelt „Wie Pfade zugewiesen werden“Du kannst beim Spawnen einen name mitgeben:
const ref = system.spawn(Foo, 'my-foo');// ref.path.toString() === 'actor-ts://my-app/user/my-foo'
const child = this.context.spawn(Bar, 'bar');// child.path.toString() === 'actor-ts://my-app/user/my-foo/bar'Wenn du den Namen weglässt, synthetisiert das Framework einen —
$anonymous-, einen Zähler pro Elternteil und zwölf zufällige
Hex-Zeichen, zum Beispiel '$anonymous-1-3f9c1a0d7b42'. Der Zähler
hält die Spawn-Reihenfolge lesbar; der Zufallsteil verhindert, dass
der Pfad von außen erratbar ist — und ein Pfad ist eine Adresse.
Drei Regeln gelten beim Namen:
-
Namen müssen unter Geschwistern eindeutig sein. Zweimal
context.spawn(Bar, 'bar')auf demselben Parent wirft einen Fehler — der Pfad ist bereits in Verwendung. -
Der Name muss ein einzelnes Pfadsegment sein. Das Framework erzwingt das und wirft bei einem Namen, der den Pfad beschädigen würde: ein Trennzeichen
/oder\, die Traversal-Segmente.und.., ein leerer Name oder ein beliebiges Steuerzeichen. Ein Pfad wird durch Verbinden der Segmente mit/gerendert und durch Splitten daran wieder zerlegt —spawn(Bar, 'a/b')würde also einen Pfad erzeugen, der von einem Kindbeines Aktorsanicht zu unterscheiden ist, und damit mit einem anderen Aktor kollidieren oder ihn imitieren, auch über die Cluster-Verbindung, wo die Gegenseite den String erneut splittet. Steuerzeichen werden abgelehnt, weil Pfade in Logs und Trace-Spans geschrieben werden. -
Der Name darf nicht mit
$beginnen. Dieses Präfix ist für die Namen reserviert, die das Framework selbst erzeugt —$anonymous-<n>-<random>ausspawnAnonymous. Ohne die Regel könnte ein selbst gewähltes'$anonymous-1-…'mit einem Namen kollidieren, den das Framework vergeben darf, oder ihn imitieren — und wer zuerst da war, hing an der Spawn-Reihenfolge. Ein$an anderer Stelle im Namen ist unproblematisch ('order$42').Die Regel betrifft Namen, die du wählst, und wird deshalb am
spawn-Aufruf geprüft, nicht aufActorPath— Pfade werden auch aus Strings rekonstruiert, die über die Cluster-Verbindung ankommen, und ein$-Verbot dort ließe jede Remote-Referenz auf einen anonymen Aktor beim Empfang scheitern.Alles andere ist erlaubt, inklusive Leerzeichen, Punkte innerhalb des Namens und Nicht-ASCII-Zeichen —
'Order.Placed','entity#3'und'日本語'sind alle gültig.
Das uid-Feld auf ActorPath unterscheidet aufeinanderfolgende
Inkarnationen desselben Pfads — wenn my-foo stoppt und dann am
selben Pfad neu gespawnt wird, hat die zweite eine andere uid.
Die meiste Code schaut nie auf uid; das Framework verwendet es
intern, um sicherzustellen, dass Nachrichten an die alte
Inkarnation nicht versehentlich in der neuen landen.
ActorSelection — Lookup per Pfad
Abschnitt betitelt „ActorSelection — Lookup per Pfad“import { ActorSystem } from 'actor-ts';
const system = ActorSystem.create('my-app');
const selection = system.actorSelection('/user/api/sessions/user-42');const ref = await selection.resolveOne(5_000);ref.tell({ kind: 'whatever' });actorSelection(pathString) baut eine Beschreibung davon, wo zu
suchen ist. resolveOne(timeoutMs) durchläuft den Actor-Baum, um
einen Match zu finden; es retried alle 10 ms bis zum Deadline,
nützlich, wenn der Aufrufer mit dem Spawn des Actors race-t.
Zwei Wege, einer Selection etwas zu senden:
selection.tell(message)— einmal auflösen, sofort liefern, wenn gefunden, in Dead Letters legen, wenn nicht. Kein Retry. Nützlich, wenn du eine klare Erwartung hast, dass der Actor existiert; ihn zu verfehlen ist ein Fehler.(await selection.resolveOne(timeout)).tell(message)— warte, bis der Actor existiert (oder bis zum Timeout). Nützlich für Spawn-Race-Szenarien.
Akzeptierte Pfadformate
Abschnitt betitelt „Akzeptierte Pfadformate“actorSelection parst drei Input-Formen:
system.actorSelection('actor-ts://my-app/user/api'); // absolute URIsystem.actorSelection('/user/api'); // absoluter Pfadsystem.actorSelection('user/api'); // absoluter Pfad, ohne führenden SlashDie URI-Form ist das, was ActorPath.toString() produziert,
Round-Tripping eines Pfads durch die String-Form funktioniert also.
Der Systemname in URI-Form wird geprüft —
actor-ts://other-app/user/api aus einem System namens my-app zu
selektieren gibt null zurück (ein komplett anderes System).
Wann Pfad verwenden, wann Ref
Abschnitt betitelt „Wann Pfad verwenden, wann Ref“Refs sind der Default-Weg, Actors zu adressieren. Halte die
ActorRef, die du beim Spawn bekommen hast, gib sie weiter, speichere
sie in Feldern. Der Compiler kennt den Nachrichtentyp des Actors;
Selection gibt unknown-typisierte Refs zurück, mit denen du
Typisierung verlierst.
Greife in drei Situationen zu einer ActorSelection:
- Cross-Tree-Lookup per Konvention — der gewünschte Actor lebt
an einem bekannten Pfad (
/user/api/sessions,/system/cluster/pubsub/mediator), und es gibt keinen sauberen Weg, die Ref durchzureichen. - Spawn-Race-Auflösung — der Aufrufer weiß nicht genau, wann
das Target gespawnt wird;
resolveOne(timeout)wartet. - Lookups von Actors, die als Strings empfangen wurden — z.B.
ein HTTP-Request-Body enthält
"/user/sessions/user-42", und du willst an diesen Actor weiterleiten. Parse + Select; vertraue dem String nie, ohne zu re-checken, dass der Actor existiert.
Für die alltägliche In-Actor-Verdrahtung bevorzuge Refs durch Konstruktoren / Nachrichten / Eltern-Kind-Beziehungen weiterzureichen:
// ✗ umständlichclass Worker extends Actor<...> { override onReceive(message) { const cache = await this.context.actorSelection('/user/cache').resolveOne(); cache.tell({ kind: 'put', ... }); }}
// ✓ direktclass Worker extends Actor<...> { constructor(private readonly cache: ActorRef<CacheMessage>) { super(); } override onReceive(message) { this.cache.tell({ kind: 'put', ... }); }}const cache = system.spawn(Cache, 'cache');const worker = system.spawnAnonymous(() => new Worker(cache));Die Konstruktor-Injection-Variante ist typsicher, hängt nicht von Pfad-Naming-Konventionen ab und überlebt Renames trivial.
Spezielle Top-Level-Pfade
Abschnitt betitelt „Spezielle Top-Level-Pfade“Vier Top-Level-Pfade existieren in jedem System:
| Pfad | Was dort lebt |
|---|---|
/user | Die Top-Level-Actors deiner Anwendung — alles, was system.spawn(...) erzeugt. |
/system | Framework-Internas, nach Subsystem gruppiert. |
/temp | Die Einweg-Antwort-Refs, die ask erzeugt — einer pro offenem Aufruf. |
/deadLetters | Der synthetische Empfänger für Nachrichten ohne lebendes Ziel. |
/temp fällt aus der Reihe: nichts darunter ist ein Actor. ask
synthetisiert einen Antwort-Ref, gibt ihm einen Pfad
/temp/askResp-<id> und wirft ihn weg, sobald der Aufruf abgeschlossen
ist — er taucht also in keinem Supervisor-Baum auf und actorSelection
findet ihn nie. Einen Pfad hat er überhaupt nur, weil eine Antwort,
die über die Cluster-Leitung geht, per Pfad adressiert wird: der Knoten
des Empfängers braucht ein Ziel zum Zurücksenden. In Logs siehst du
diese Refs als sender einer per ask verschickten Nachricht.
Innerhalb von /system
Abschnitt betitelt „Innerhalb von /system“Alles, was das Framework für sich selbst erzeugt, liegt hier — eine Gruppe pro Subsystem, sodass der Baum verrät, zu welchem Teil des Frameworks ein Actor gehört:
| Pfad | Actor |
|---|---|
/system/cluster/receptionist | Der Receptionist. |
/system/cluster/pubsub/mediator | Der Distributed-PubSub-Mediator. |
/system/cluster/crdt/data | Die DistributedData-Replika. |
/system/cluster/sharding/region-<typeName> | Eine Shard-Region. |
/system/cluster/sharding/coordinator-<typeName> | Ein Shard-Coordinator. |
/system/cluster/singleton/manager-<typeName> | Ein Cluster-Singleton-Manager (der Singleton selbst ist dessen Kind). |
/system/persistence/projection/<name> | Eine Projection. |
/system/delivery/consumer-<n>-<random>, producer-<n>-<random> | Reliable-Delivery-Controller, wenn du sie nicht selbst benennst. |
/system/devtools/hub | Der DevTools-WebSocket-Hub, mit seinen Probes daneben. |
Zwei Dinge folgen daraus, die beim Lesen eines Baums helfen:
- Die Gruppen-Ebenen sind Actors.
/system/cluster/shardingist ein leerer Supervisor, dessen ganze Aufgabe darin besteht, die Gruppierung und die Supervisions-Strategie für alles darunter zu halten. Er hat Kinder und verarbeitet nie eine Nachricht. - Gruppen entstehen bei der ersten Nutzung. Ein System, das nie
Clustering oder DevTools startet, hat ein leeres
/system— das ist der Normalzustand, kein fehlender Ast.
Du adressierst /system-Pfade selten direkt: die Extensions (Cluster,
PubSub, Sharding) bieten typisierte APIs, die dir Refs geben. Aber die
Kenntnis des Layouts zahlt sich beim Lesen von Log-Output aus — der
Logger jedes Actors ist an seinen Pfad gebunden — oder bei der Frage “wo
ist diese Nachricht hingegangen?”.
Cluster-Pfade
Abschnitt betitelt „Cluster-Pfade“Wenn die Cluster-Extension aktiv ist, bekommen Pfade ein Host-Port-Fragment:
actor-ts://my-app@10.0.0.5:2552/user/api/sessions/user-42| Segment | Was es ist |
|---|---|
actor-ts:// | Das Schema — gleich wie bei lokalen Pfaden. |
my-app | Der Systemname des Clusters. |
@10.0.0.5:2552 | Host:Port des Nodes, zur Cluster.join-Zeit zugewiesen. |
/user/api/sessions/user-42 | Der Actor-Pfad innerhalb dieses Nodes, in der Form identisch zu einem Single-Node-Pfad. |
Dieses Fragment sagt der Runtime, auf welchem Node dieser Actor
lebt. Ein nackter Pfad wie actor-ts://my-app/user/foo (ohne Host)
ist lokal zum auflösenden System. Der Cluster-Transport handhabt die
host-geroutete Auslieferung transparent — dein tell sieht in beiden
Fällen gleich aus.
Siehe Refs zwischen Nodes dafür, wie der host-aware Pfad wire-encoded wird.
Wie es weitergeht
Abschnitt betitelt „Wie es weitergeht“- Actors erzeugen — das
Konfigurations-Bundle, das den optionalen
namefür den Pfad des Actors enthält. - Actor-System — die
Guardian-Hierarchie (
/user,/system,/deadLetters). - Refs zwischen Nodes — wie Pfade und Refs über Cluster-Nodes hinweg funktionieren.
- Discovery — für Actors, deren Position nicht per Pfad, sondern per Service-Registry-Semantik bekannt ist.
Die ActorPath- und
ActorSelection-API-Referenzen
decken den vollständigen Method-Set ab.
