Konfiguration
actor-ts verwendet HOCON zur Konfiguration — eine Obermenge von
JSON, die in JVM-Config-Dateien üblich ist, mit Substitutionen,
Durations und Sizes. (include-Direktiven werden
abgelehnt — setze die Quellen im
Code zusammen.) Drei Schichten, von oben nach unten aufgelöst:
- Konstruktor-Argumente an
ActorSystem.create(name, settings)— explizite Code-Overrides. - Nutzer-Config —
application.confim Projekt-Root, oder ein expliziter Pfad viaconfigFile, oder ein inlineconfig-Objekt in den Settings. - Reference-Defaults — im Framework gebündelt als
REFERENCE_CONF(siehesrc/config/reference.ts).
Alles, was nicht in Schicht 1 oder 2 spezifiziert ist, fällt auf den Reference-Default zurück.
Diese Seite erklärt, was die einzelnen Keys tun. Die vollständige Liste — jede Einstellung, die das Framework mitbringt, wörtlich aus der gebündelten Datei — steht unter Die vollständige reference.conf.
Eine vollständige application.conf
Abschnitt betitelt „Eine vollständige application.conf“actor-ts { system { name = "my-app" } logger { level = "info" }
dispatcher { default = "immediate" # immediate | microtask | throughput throughput = 16 }
cluster { gossip-interval = 1s seed-retry-interval = 3s weakly-up-after = 0s # 0 = opt-in only max-members = 1000 max-tombstones = 10000 tombstone { time-to-live = 24h prune-interval = 5m min-retention = 0s # 0 = derive from down-after } failure-detector { heartbeat-interval = 500ms unreachable-after = 2s down-after = 5s } pub-sub { gossip-interval = 1s max-subscribers-per-topic = 10000 max-topics = 10000 max-remote-nodes-per-topic = 1000 send-to-dead-letters-when-no-subscribers = on } receptionist { gossip-interval = 1s max-subscribers-per-key = 1000 max-subscribers-total = 10000 } }
distributed-data { gossip-interval = 1s max-pending-quorum-requests = 1000 # 0 = kein Cap max-quorum-timeout = 30s # 0 = keine Obergrenze }
remote { tcp { host = "0.0.0.0" port = ${?ACTOR_TS_PORT} # Env-Var-Substitution; fällt auf Default zurück } max-frame-bytes = 16M }
persistence { journal { plugin = "actor-ts.persistence.journal.in-memory" } snapshot-store { plugin = "actor-ts.persistence.snapshot-store.in-memory" } }
sharding { number-of-shards = 64 rebalance-interval = 2s hand-off-timeout = 10s remember-entities = false passivation-idle = 5m # 0 deaktiviert die Auto-Passivierung # shard-passivation-idle # ungesetzt: folgt passivation-idle max-entities = 0 # 0 = kein Cap pro Node }}Platziere das im Projekt-Root. Das Framework lädt es bei
ActorSystem.create automatisch.
Die Keys, gruppiert nach Modul
Abschnitt betitelt „Die Keys, gruppiert nach Modul“actor-ts.system
Abschnitt betitelt „actor-ts.system“| Key | Default | Zweck |
|---|---|---|
system.name | "default" | Systemname — verwendet in Actor-Pfaden und zur Cluster-Identifikation. Greift, wenn ActorSystem.create() ohne Namen aufgerufen wird; ein explizites create('billing') gewinnt. |
actor-ts.logger
Abschnitt betitelt „actor-ts.logger“| Key | Default | Zweck |
|---|---|---|
logger.level | "info" | Einer von debug / info / warn / error / off. |
actor-ts.dispatcher
Abschnitt betitelt „actor-ts.dispatcher“| Key | Default | Zweck |
|---|---|---|
dispatcher.default | "immediate" | immediate (Default) / microtask / throughput. |
dispatcher.throughput | 16 | Nachrichten, die ein ThroughputDispatcher verarbeitet, bevor er nachgibt. |
actor-ts.cluster
Abschnitt betitelt „actor-ts.cluster“| Key | Default | Zweck |
|---|---|---|
cluster.gossip-interval | 1s | Wie oft Gossip mit einem zufälligen Peer ausgetauscht wird. |
cluster.seed-retry-interval | 3s | Wie oft Seed-Verbindungen während des Joins wiederholt werden. |
cluster.weakly-up-after | 0s | Befördert ein joining-Mitglied nach dieser Zeit automatisch zu weakly-up. 0 schaltet das ab — siehe Weakly-up. |
cluster.max-members | 1000 | Obergrenze für lebende Mitgliedseinträge, die Gossip anlegen darf. 0 schaltet die Grenze ab. |
cluster.max-tombstones | 10000 | Obergrenze für removed-Tombstones, die Gossip anlegen darf. 0 schaltet die Grenze ab. |
cluster.tombstone.time-to-live | 24h | Wie lange ein removed-Tombstone gehalten wird, bevor er gepruned wird. |
cluster.tombstone.prune-interval | 5m | Wie oft der Prune-Durchlauf läuft. |
cluster.tombstone.min-retention | 0s | Mindestalter, bevor ein Tombstone gepruned werden darf. 0 leitet es aus failure-detector.down-after ab (6×). |
cluster.failure-detector.heartbeat-interval | 500ms | Wie oft Heartbeats gesendet werden. |
cluster.failure-detector.unreachable-after | 2s | Verdachtsschwelle für unreachable-Status. |
cluster.failure-detector.down-after | 5s | Verdachtsschwelle für erzwungenes Down. Wird ab dem letzten Heartbeat gemessen, muss also größer als unreachable-after sein. |
Diese Keys liegen unter dem, was Cluster.join(system, options) übergibt —
ein explizites withGossipIntervalMs(…) gewinnt also weiterhin. Der
Failure Detector wird pro Schwelle gemergt: setzt du im Code nur
down-after, bleiben die beiden anderen aus der Datei erhalten.
HOCON-Baum und ClusterOptions-Felder sind bewusst nicht deckungsgleich:
die drei Tombstone-Stellschrauben stehen unter tombstone, weil man sie so
einstellt, während die Felder flach bleiben (withTombstoneTtlMs(…), …).
remote.tcp.host → host macht dieselbe Übersetzung schon länger.
min-retention = 0s heißt leite die Untergrenze aus dem Failure Detector
ab, nicht keine Untergrenze — also genau das, was ein ungesetztes Feld
bedeutet. Eine Datei, die den Default ausschreibt, verhält sich damit wie
eine, die ihn wegläßt.
Die beiden max-*-Grenzen begrenzen die lokale Mitgliederkarte.
remote.max-frame-bytes begrenzt einen Gossip-Frame; diese begrenzen,
was eine Folge wohlgeformter Frames ansammeln kann — eine andere Größe,
siehe Cluster-Sicherheit.
seeds, roles, der Transport und der Downing Provider haben keine
HOCON-Form — die ersten beiden sind Deployment-Identität, die an die
Join-Stelle gehört, die letzten beiden sind Objekte, die eine
Config-Datei nicht ausdrücken kann.
actor-ts.cluster.pub-sub
Abschnitt betitelt „actor-ts.cluster.pub-sub“| Key | Default | Zweck |
|---|---|---|
cluster.pub-sub.gossip-interval | 1s | Wie oft ein Mediator seine Topic-Menge an einen zufälligen Peer schickt. |
cluster.pub-sub.max-subscribers-per-topic | 10000 | Obergrenze für lokale Subscriber eines Topics. Ein Subscribe darüber wird mit SubscribeRejected beantwortet. |
cluster.pub-sub.max-topics | 10000 | Obergrenze für verschiedene Topics eines Mediators — gilt für gegossipte Topic-Ansprüche genauso wie für lokale Subscribes. |
cluster.pub-sub.max-remote-nodes-per-topic | 1000 | Obergrenze für Peers, die Subscriber für ein Topic beanspruchen dürfen. |
cluster.pub-sub.send-to-dead-letters-when-no-subscribers | on | Ein Publish, das niemanden erreicht hat, geht an system.deadLetters, statt verworfen zu werden. |
Die drei Obergrenzen begrenzen, was ein Mediator überhaupt halten kann,
und der Publish-Fan-out läuft über alle — sie sind also ebenso sehr eine
Latenz- wie eine Speichergrenze. Die Gossip-Hälfte ist die, die man
kennen sollte: ein Peer, der Topics ankündigt, für die er Subscriber
beansprucht, hat früher auf jedem Empfänger einen Eintrag pro Namen
angelegt — ganz ohne lokales Subscribe. Siehe
DistributedPubSub.
actor-ts.cluster.receptionist
Abschnitt betitelt „actor-ts.cluster.receptionist“| Key | Default | Zweck |
|---|---|---|
cluster.receptionist.gossip-interval | 1s | Wie oft der Receptionist seine lokalen Registrierungen gossipt. |
cluster.receptionist.max-subscribers-per-key | 1000 | Obergrenze für Subscriber eines Service Keys. Ein Subscribe darüber wird mit ReceptionistSubscribeRejected beantwortet. |
cluster.receptionist.max-subscribers-total | 10000 | Obergrenze für Subscriber über alle Keys dieses Nodes. |
Beide Blöcke liegen unter dem, was dem start(...) der Extension
übergeben wurde — ein explizites withMaxTopics(…) gewinnt also
weiterhin. Gestoppte Subscriber werden vom Death Watch freigegeben und
zählen gegen keine der Grenzen.
actor-ts.distributed-data
Abschnitt betitelt „actor-ts.distributed-data“| Key | Default | Zweck |
|---|---|---|
distributed-data.gossip-interval | 1s | Wie oft eine Replika ihren kompletten Key-Satz an einen zufälligen Peer schickt. |
distributed-data.max-pending-quorum-requests | 1000 | Obergrenze für offene Quorum-Requests — updateAsync und getAsync teilen sich ein Budget. Ein Request darüber wird direkt abgelehnt. 0 deaktiviert das Cap. |
distributed-data.max-quorum-timeout | 30s | Obergrenze für das timeoutMs eines Aufrufs; ein größerer Wert wird darauf heruntergeschnitten. 0 deaktiviert die Obergrenze. |
Der Block liegt auf oberster Ebene statt unter cluster.* (wo pub-sub
und receptionist wohnen), weil das Modul dort liegt: DistributedData
kommt aus src/crdt/, und der Cluster ist ein positionales Argument von
start(cluster), kein Tunable davon. durableStore hat hier keinen Key —
es ist eine DurableStateStore-Instanz, die eine Config-Datei nicht
ausdrücken kann.
Beide Caps begrenzen die Menge der offenen Requests selbst. Jeder offene
Quorum-Request hält ein Promise, einen Timer und ein Ziel-Set am Leben, bis
seine Deadline abläuft — ein ungedeckelter Replikator unter Last sammelt also
alle drei an, ohne dass irgendetwas ihn bremst. Wer über
max-pending-quorum-requests hinaus ablehnt, macht aus dem, was später als
Timeout-Sturm ankäme, sofortige Fehler, die den Knopf beim Namen nennen.
Siehe Quorum-Reads und -Writes.
max-quorum-timeout begrenzt die andere Achse. Ein offener Request hält
seinen Slot über die gesamte Deadline, ein einzelner Aufrufer mit einem
stundenlangen timeoutMs parkt also das Budget für Stunden und sperrt
spätere Aufrufer aus, die dem Cap selbst nie nahe kamen.
actor-ts.remote
Abschnitt betitelt „actor-ts.remote“| Key | Default | Zweck |
|---|---|---|
remote.tcp.host | "0.0.0.0" | Bind-Adresse. Wird verwendet, wenn ClusterOptions host nicht setzt. |
remote.tcp.port | 2552 | Bind-Port. Wird verwendet, wenn ClusterOptions port nicht setzt. |
remote.max-frame-bytes | 16M | Wire-Cap pro Frame. Ein Frame, dessen Längen-Präfix darüber liegt, wird verworfen, bevor irgendeine Payload gepuffert wird. |
remote.tls.enabled | false | Nicht implementiert — siehe unten. |
Weil die Bind-Adresse aus der Config lesbar ist, kann ein Deployment sie komplett aus dem Code herausziehen:
actor-ts.remote.tcp { host = "0.0.0.0" port = ${?ACTOR_TS_PORT} # Env-Var-Substitution}remote.max-frame-bytes gilt für den Transport, den der Cluster sich
selbst baut. Wenn du einen TcpTransport selbst konstruierst und per
withTransport(…) übergibst, setz das Cap dort — das Framework re-capped
keinen Transport, den es nicht erzeugt hat.
actor-ts.http
Abschnitt betitelt „actor-ts.http“| Key | Default | Zweck |
|---|---|---|
http.backend | "fastify" | Einer von fastify / express / hono. |
http.shutdown-grace-period | 0ms | Wie lange unbind() in-flight Requests leerlaufen lässt, bevor Verbindungen erzwungen geschlossen werden. 0 erzwingt sofort. |
http.websocket | (siehe unten) | Serverseitige WebSocket-Defaults für websocket()-Routen. |
http.backend entscheidet nur darüber, was newServerAt(...).bind()
verwendet, wenn der Builder kein eigenes Backend bekommen hat —
useBackend(new HonoBackend()) gewinnt immer und bleibt der einzige Weg,
ein selbst konfiguriertes oder selbst geschriebenes Backend zu übergeben.
Ein unbekannter Name lässt bind() mit einem ConfigError scheitern,
statt still auf Fastify zurückzufallen. Express und Hono sind optionale
Peer-Dependencies und werden nur importiert, wenn du sie benennst.
Die Grace Period ist per Default 0 — sofort erzwingen —, weil unbind()
genau das in der Praxis immer getan hat. Erhöhe sie, wenn in-flight
Requests fertig werden sollen.
Meist ist sie eine Obergrenze und keine Wartezeit: unbind() kehrt zurück,
sobald der Server tatsächlich geschlossen ist. Wo das close() eines
Backends nicht settlen kann — etwa Express mit einem lebenden WebSocket —
wird das Fenster zu einer Deadline, die immer erreicht wird; ein großer Wert
ist dort also Zeit, die du immer bezahlst. Ein Aufrufer, der einen eigenen
Wert übergibt — binding.unbind(1_000) — überschreibt die Config.
Serverseitige WebSocket-Routen (websocket(path, actorRef)) lesen
ihre Defaults aus actor-ts.http.websocket:
| Key | Default | Zweck |
|---|---|---|
http.websocket.maxFrameBytes | 1M | Größter akzeptierter eingehender Frame (Bytes). |
http.websocket.onOversizeFrame | "close" | Was passiert, wenn ein Frame das Limit überschreitet — close / drop. |
http.websocket.onInvalidMessage | "close" | Was passiert, wenn eine Nachricht nicht dekodiert werden kann — close / drop / hook. |
http.websocket.maxBufferedBytes | 4M | Outbound-Backpressure-Schwelle (Bytes). |
http.websocket.onBackpressure | "drop" | Was passiert, wenn der Buffer überschritten wird — drop / close. |
http.websocket.maxConnections | (unbegrenzt) | Max. gleichzeitige Verbindungen pro Route; ein Upgrade darüber hinaus wird mit 1013 geschlossen. |
actor-ts.persistence
Abschnitt betitelt „actor-ts.persistence“| Key | Default | Zweck |
|---|---|---|
persistence.journal.plugin | "...in-memory" | Voll qualifizierter Key zur Konfig des Journals. |
persistence.snapshot-store.plugin | "...in-memory" | Voll qualifizierter Key zur Konfig des Snapshot Stores. |
Die Plugin-Keys zeigen auf einen anderen Config-Abschnitt, der
die Settings dieses Plugins enthält. Z. B. enthält
actor-ts.persistence.journal.sqlite die path, pragmas etc.
des SQLite-Journals. Siehe jede Journal-Seite in
Persistence für die per-Plugin-Keys.
actor-ts.cache
Abschnitt betitelt „actor-ts.cache“| Key | Default | Zweck |
|---|---|---|
cache.in-memory.maxEntries | 10000 | Max. Einträge vor LRU-Eviction (In-Memory-Cache). |
cache.in-memory.cleanupMs | 60000 | Intervall (ms) des Hintergrund-Sweeps für abgelaufene Einträge. |
Redis- und Memcached-Caches lesen ihre Verbindungs-Settings aus
actor-ts.cache.redis / actor-ts.cache.memcached.
actor-ts.sharding
Abschnitt betitelt „actor-ts.sharding“| Key | Default | Zweck |
|---|---|---|
sharding.number-of-shards | 64 | In wie viele Shards der Entity-Raum geteilt wird. |
sharding.rebalance-interval | 2s | Abstand zwischen koordinator-getriebenen Rebalance-Passes. |
sharding.hand-off-timeout | 10s | Wie lange auf HandOffComplete gewartet wird, bevor zwangsumverteilt wird. |
sharding.remember-entities | false | Persistiere die Menge aktiver Entity-IDs. |
sharding.passivation-idle | 5m | Auto-Passiviere eine Entität nach diesem Idle-Fenster. 0 = deaktiviert. Eine Entität, die Zustand im Speicher hält und ihn nicht in preStart wiederaufbaut, verliert ihn dabei — siehe Passivierung. |
sharding.shard-passivation-idle | (ungesetzt) | Auto-Passiviere einen Shard, nachdem er so lange leer stand. Ungesetzt folgt er passivation-idle; 0 hält leere Shards resident. Bewusst nicht in der reference.conf, denn genau von einem ausgelieferten Wert müsste sich „ungesetzt” unterscheiden lassen. |
sharding.max-entities | 0 | Entity-Obergrenze pro Node; bei Überlauf wird die am längsten ungenutzte Entität passiviert. 0 = kein Cap. |
Diese Keys gelten für jeden auf dem Node gestarteten Sharded Type. Sie
werden einmal pro sharding.start(...) gelesen und unter das gelegt, was
dieser Aufruf explizit übergibt — die übliche Reihenfolge gilt also pro Feld:
// actor-ts.sharding.passivation-idle = 2 minutes
const shardingOptions = StartShardingOptions.create<CartCommand>() .withTypeName('cart') .withEntityActor(CartEntity) .withExtractEntityId((command) => command.entityId) .withNumShards(256);
cluster.sharding.start(shardingOptions);// numShards 256 (explizit), passivationIdleMs 120_000 (Config-Datei)Ein Setting für genau einen Typ gehört deshalb in den Builder; die Config-Datei ist für die node-weite Grundeinstellung, die du ohne Rebuild zwischen Staging und Produktion verschieben willst. Die polymorphen Optionen — der Entity-Actor, die Extraktoren, die Allocation Strategy, ein Lease, die Stores — haben keine HOCON-Form und bleiben im Code.
actor-ts.coordinated-shutdown
Abschnitt betitelt „actor-ts.coordinated-shutdown“| Key | Default | Zweck |
|---|---|---|
coordinated-shutdown.default-phase-timeout | 5s | Default-Timeout pro Phase. Eine Phase, die es überschreitet, wird abgebrochen, damit die nächste noch läuft. |
coordinated-shutdown.terminate-actor-system | true | Ob der eingebaute Terminator-Task in der finalen Phase system.terminate() aufruft. |
coordinated-shutdown.exit-process | false | Ob nach Abschluss der Pipeline process.exit(0) aufgerufen wird. |
terminate-actor-system = false entfernt nur den eingebauten Terminator —
in ActorSystemTerminate registrierte eigene Tasks laufen weiterhin.
Nutze es, wenn ein Host-Prozess die Lebensdauer des Systems besitzt und ein
Signal-Handler es nicht killen darf.
exit-process ist für den umgekehrten Fall: ein hängengebliebenes Handle
(ein offener Socket, eine gepoolte Verbindung, die ein Treiber nie
freigegeben hat) hält den Prozess am Leben, nachdem die Pipeline fertig
ist — von außen nicht von einem Hänger zu unterscheiden. Per Default aus,
weil ein erzwungener Exit genau die Leaks verdeckt, die du eigentlich
finden willst.
actor-ts.worker-cluster (Multi-Runtime-Worker)
Abschnitt betitelt „actor-ts.worker-cluster (Multi-Runtime-Worker)“| Key | Default | Zweck |
|---|---|---|
worker-cluster.workers | "auto" | Anzahl Worker — "auto" verwendet navigator.hardwareConcurrency. |
worker-cluster.restart-policy | "on-failure" | always / on-failure / never. |
Diese Keys füllen WorkerCluster.spawn(options) für Felder auf, die der
Aufruf nicht setzt. spawn ist ein Static ohne ActorSystem im Scope und
lädt die Config-Kette daher selbst — dieselbe, die ActorSystem.create
verwendet, inklusive ACTOR_TS_CONFIG und ./application.conf. Eine
unbekannte restart-policy wird mit einem OptionsError abgelehnt, statt
still „never” zu bedeuten.
Broker-Plugins — actor-ts.io.broker.*
Abschnitt betitelt „Broker-Plugins — actor-ts.io.broker.*“Jeder Broker-Actor liest aus seinem eigenen Subtree. Siehe die per-Protokoll-Seiten für die Keys:
| Subtree | Seite |
|---|---|
io.broker.kafka | Kafka |
io.broker.mqtt | MQTT |
io.broker.amqp | AMQP |
io.broker.nats | NATS |
io.broker.jetstream | NATS JetStream |
io.broker.jetstream-key-value | JetStream KV + Object Store |
io.broker.jetstream-object-store | JetStream KV + Object Store |
io.broker.redis-streams | Redis Streams |
io.broker.grpc.{client,server} | gRPC |
io.broker.websocket | WebSocket Client |
io.broker.sse | SSE |
io.broker.tcp | TCP |
io.broker.udp | UDP |
HOCON-spezifische Syntax
Abschnitt betitelt „HOCON-spezifische Syntax“Durations
Abschnitt betitelt „Durations“gossip-interval = 1s # 1000 msunreachable-after = 2.5s # 2500 msdown-after = 5000ms # explizite msgc-cadence = 10m # Minutenttl = 24h # StundenErkannte Einheiten: ns, us, ms, s, m, h, d.
max-frame-bytes = 16M # 16 777 216 Bytesbuffer = 64Kheap = 2GErkannte Einheiten: B, K, M, G, T — standardmäßig
binär (1024).
Environment-Substitution
Abschnitt betitelt „Environment-Substitution“port = ${?ACTOR_TS_PORT} # nutze env, falls gesetzt, sonst fallbacklog-level = ${?LOG_LEVEL} # gleichapi-key = ${API_KEY} # required — Fehler, wenn env nicht gesetztfallback-port = ${?ENV_PORT}fallback-port = ${fallback-port:-2552} # default-if-empty SyntaxVerwende ${?ENV} für optional, ${ENV} für required.
Includes werden nicht unterstützt
Abschnitt betitelt „Includes werden nicht unterstützt“include "shared-cluster.conf" # Fehler: `include` wird nicht unterstütztEine Config-Quelle kann keine weitere Datei und keine URL benennen, die
hinzugeladen wird. Das ist eine Entscheidung, keine Lücke: ein
aufgelöstes include überließe demjenigen, der die Config schreibt, die
Wahl, welche Pfade der Prozess liest — relativ zu einer Wurzel, die der
Parser gar nicht kennen kann. Das Zusammenführen im Code belässt diese
Wahl beim Aufrufer.
import { Config } from 'actor-ts';
const shared = Config.parseFile('shared-cluster.conf');const application = Config.parseFile('application.conf');const config = shared.merge(application);merge legt sein Argument obenauf, die lokale Datei gewinnt also — genau
die Reihenfolge, auf die das include oben hinauswollte. Den geteilten
Inhalt in eine Datei zu kopieren funktioniert ebenfalls.
Der Parser sagt all das, wenn er auf ein include trifft, und zitiert
dabei Zeile und Ziel — eine aus Akka oder Pekko portierte Config
scheitert damit mit einer Antwort statt mit einem Rätsel.
Reservierte Schlüssel
Abschnitt betitelt „Reservierte Schlüssel“Drei Schlüssel werden in einer Config-Quelle überall abgelehnt — als Schlüssel, als jedes Segment eines Pfadausdrucks und innerhalb einer Substitution:
__proto__.anything = 1 # Fehler: abgelehntconstructor.x = 1 # Fehler: abgelehntprototype = 1 # Fehler: abgelehntvalue = ${__proto__} # Fehler: abgelehntEin Schlüsselpfad wird auf ein einfaches Objekt expandiert, und eine
Zuweisung an __proto__ schreibt in den Objekt-Prototypen statt einen
normalen Schlüssel anzulegen — würde man das zulassen, könnte eine
Config-Datei das Verhalten jedes Objekts im Prozess verändern. Der
Parser lehnt sie stattdessen mit der gewohnten Zeilen- und
Spaltenangabe ab.
Die Prüfung vergleicht exakt, ähnlich aussehende Schlüssel sind also
unproblematisch: _proto_, constructorName und prototypes
funktionieren normal.
Config im Code lesen
Abschnitt betitelt „Config im Code lesen“const actorSystemOptions = ActorSystemOptions.create().withConfig({ 'actor-ts.cluster.gossip-interval': '500ms', });const system = ActorSystem.create( 'my-app', actorSystemOptions,);
// Innerhalb eines Actors / einer Extension:const config = system.config;const interval = config.getDuration('actor-ts.cluster.gossip-interval');// → 500 (in ms)Das Config-Interface hat typisierte Getter: getString, getNumber,
getBoolean, getDuration, getBytes, getStringList, etc.
Fehlende Keys werfen, es sei denn, Du prüfst zuerst mit hasPath.
Ladereihenfolge
Abschnitt betitelt „Ladereihenfolge“1. REFERENCE_CONF ← gebündelte Defaults2. application.conf (CWD) ← Projekt-Root, auto-geladen3. Dateipfad aus `configFile`-Setting ← expliziter Override4. ENV-Var ACTOR_TS_CONFIG ← Pfad oder inline HOCON5. Konstruktor `config: { ... }` ← Code-Level-Override6. Konstruktor-Feld-Overrides ← explizite Felder schlagen Config (logLevel, dispatcher, scheduler, …)Jede Schicht überlagert die vorherige. Schicht 6 (Konstruktor-Felder) gewinnt immer für die Felder, die sie abdeckt; alles andere fließt durch den HOCON-Merge.
Wo der eingebaute Default steht
Abschnitt betitelt „Wo der eingebaute Default steht“Schicht 1 ist reference.conf — aber nicht jede Einstellung hat dort
einen Key, und selbst die, die einen haben, brauchen einen Wert im Code
für den Fall, dass nichts konfiguriert ist. Dieser Wert hat einen festen
Ort, damit immer auffindbar ist, worauf eine Einstellung zurückfällt:
- Options-Defaults stehen neben dem Feld, das sie belegen, in der
passenden
XOptions.ts.DEFAULT_MAX_ENTRIESliegt inInMemoryCacheOptions.ts,DEFAULT_NUM_SHARDSinShardingOptions.ts. - Alles andere — Obergrenzen, Schranken und Timeouts ohne Option
dahinter — steht in einer
Constants.ts:src/<subsystem>/Constants.tsfür ein einzelnes Subsystem,src/util/Constants.tsfür Werte, die mehrere teilen.
Ein reference.conf-Blatt und sein Code-Fallback sind damit immer genau
zwei Dateien, und die zweite ist nach der ersten benannt.
Validierung
Abschnitt betitelt „Validierung“Optionswerte werden einmal validiert, zur Consume-Zeit, auf den
gemergten Settings — nachdem die drei obigen Ebenen aufgelöst sind.
Weil die Prüfung auf dem gemergten Ergebnis läuft, gelten dieselben
Regeln, egal wie ein Wert ankam: über den fluenten Builder, ein einfaches
Settings-Objekt oder einen HOCON-Key. Cross-Field-Regeln (z. B.
„downThreshold muss unreachableThreshold übersteigen”) sehen die
finalen Werte.
Ein ungültiger Wert wirft einen OptionsError (aus dem
Paket-Root exportiert), der das betroffene Feld und die Erwartung nennt:
MqttOptions: protocolVersion must be one of 4, 5 (got 6)- Bei Broker-Aktoren (MQTT, Kafka, WebSocket-Client, …) wird der
Fehler beim Start des Aktors (
preStart) geworfen, nach der Pflichtfeld-Prüfung — ein fehlendes Pflichtfeld erscheint also weiterhin alsBrokerOptionsError, und nur vorhandene, aber ungültige Werte werden zuOptionsError. - Bei Nicht-Broker-Konsumenten (Caches, Leases, Sharding, …) wird der Fehler aus dem Konstruktor geworfen.
OptionsError ist verschieden von ConfigError (fehlerhaftes HOCON —
falscher Typ, fehlender Pfad) und BrokerOptionsError (ein
Broker-Pflichtfeld fehlt in jeder Ebene). Ungesetzte optionale Felder
lösen ihn nie aus; sie fallen auf ihre Defaults zurück.
import { CircuitBreaker, CircuitBreakerOptions, OptionsError } from 'actor-ts';
// Nicht-Broker-Consumer validieren in ihrem Konstruktor, daher wird der// Fehler synchron geworfen und ein einfaches try/catch funktioniert:try { const options = CircuitBreakerOptions.create() .withMaxFailures(0); // ungültig — muss eine positive Ganzzahl sein new CircuitBreaker(options); // validiert im Konstruktor} catch (e) { if (e instanceof OptionsError) { console.error(`bad option ${e.field}: ${e.message}`); }}// Bei einem Broker-Actor wird der OptionsError erst in preStart geworfen// und als ActorInitializationError verpackt — er taucht also über die// Supervision auf (nicht im try/catch des Spawn-Aufrufs).Wie es weitergeht
Abschnitt betitelt „Wie es weitergeht“- Actor System — wie Settings das Framework erreichen.
- Cluster Overview — die Keys, die für Multi-Node-Setups am relevantesten sind.
- Persistence Overview — Journal- + Snapshot-Store-Plugin-Auswahl.
- Versionspolitik — was stabil vs. experimentell ist.
