Zum Inhalt springen
Deutsch

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:

  1. Konstruktor-Argumente an ActorSystem.create(name, settings) — explizite Code-Overrides.
  2. Nutzer-Configapplication.conf im Projekt-Root, oder ein expliziter Pfad via configFile, oder ein inline config-Objekt in den Settings.
  3. Reference-Defaults — im Framework gebündelt als REFERENCE_CONF (siehe src/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.

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.

KeyDefaultZweck
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.
KeyDefaultZweck
logger.level"info"Einer von debug / info / warn / error / off.
KeyDefaultZweck
dispatcher.default"immediate"immediate (Default) / microtask / throughput.
dispatcher.throughput16Nachrichten, die ein ThroughputDispatcher verarbeitet, bevor er nachgibt.
KeyDefaultZweck
cluster.gossip-interval1sWie oft Gossip mit einem zufälligen Peer ausgetauscht wird.
cluster.seed-retry-interval3sWie oft Seed-Verbindungen während des Joins wiederholt werden.
cluster.weakly-up-after0sBefördert ein joining-Mitglied nach dieser Zeit automatisch zu weakly-up. 0 schaltet das ab — siehe Weakly-up.
cluster.max-members1000Obergrenze für lebende Mitgliedseinträge, die Gossip anlegen darf. 0 schaltet die Grenze ab.
cluster.max-tombstones10000Obergrenze für removed-Tombstones, die Gossip anlegen darf. 0 schaltet die Grenze ab.
cluster.tombstone.time-to-live24hWie lange ein removed-Tombstone gehalten wird, bevor er gepruned wird.
cluster.tombstone.prune-interval5mWie oft der Prune-Durchlauf läuft.
cluster.tombstone.min-retention0sMindestalter, bevor ein Tombstone gepruned werden darf. 0 leitet es aus failure-detector.down-after ab (6×).
cluster.failure-detector.heartbeat-interval500msWie oft Heartbeats gesendet werden.
cluster.failure-detector.unreachable-after2sVerdachtsschwelle für unreachable-Status.
cluster.failure-detector.down-after5sVerdachtsschwelle 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.hosthost 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.

KeyDefaultZweck
cluster.pub-sub.gossip-interval1sWie oft ein Mediator seine Topic-Menge an einen zufälligen Peer schickt.
cluster.pub-sub.max-subscribers-per-topic10000Obergrenze für lokale Subscriber eines Topics. Ein Subscribe darüber wird mit SubscribeRejected beantwortet.
cluster.pub-sub.max-topics10000Obergrenze 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-topic1000Obergrenze für Peers, die Subscriber für ein Topic beanspruchen dürfen.
cluster.pub-sub.send-to-dead-letters-when-no-subscribersonEin 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.

KeyDefaultZweck
cluster.receptionist.gossip-interval1sWie oft der Receptionist seine lokalen Registrierungen gossipt.
cluster.receptionist.max-subscribers-per-key1000Obergrenze für Subscriber eines Service Keys. Ein Subscribe darüber wird mit ReceptionistSubscribeRejected beantwortet.
cluster.receptionist.max-subscribers-total10000Obergrenze 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.

KeyDefaultZweck
distributed-data.gossip-interval1sWie oft eine Replika ihren kompletten Key-Satz an einen zufälligen Peer schickt.
distributed-data.max-pending-quorum-requests1000Obergrenze 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-timeout30sObergrenze 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.

KeyDefaultZweck
remote.tcp.host"0.0.0.0"Bind-Adresse. Wird verwendet, wenn ClusterOptions host nicht setzt.
remote.tcp.port2552Bind-Port. Wird verwendet, wenn ClusterOptions port nicht setzt.
remote.max-frame-bytes16MWire-Cap pro Frame. Ein Frame, dessen Längen-Präfix darüber liegt, wird verworfen, bevor irgendeine Payload gepuffert wird.
remote.tls.enabledfalseNicht 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.

KeyDefaultZweck
http.backend"fastify"Einer von fastify / express / hono.
http.shutdown-grace-period0msWie 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:

KeyDefaultZweck
http.websocket.maxFrameBytes1MGröß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.maxBufferedBytes4MOutbound-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.
KeyDefaultZweck
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.

KeyDefaultZweck
cache.in-memory.maxEntries10000Max. Einträge vor LRU-Eviction (In-Memory-Cache).
cache.in-memory.cleanupMs60000Intervall (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.

KeyDefaultZweck
sharding.number-of-shards64In wie viele Shards der Entity-Raum geteilt wird.
sharding.rebalance-interval2sAbstand zwischen koordinator-getriebenen Rebalance-Passes.
sharding.hand-off-timeout10sWie lange auf HandOffComplete gewartet wird, bevor zwangsumverteilt wird.
sharding.remember-entitiesfalsePersistiere die Menge aktiver Entity-IDs.
sharding.passivation-idle5mAuto-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-entities0Entity-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.

KeyDefaultZweck
coordinated-shutdown.default-phase-timeout5sDefault-Timeout pro Phase. Eine Phase, die es überschreitet, wird abgebrochen, damit die nächste noch läuft.
coordinated-shutdown.terminate-actor-systemtrueOb der eingebaute Terminator-Task in der finalen Phase system.terminate() aufruft.
coordinated-shutdown.exit-processfalseOb 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.

KeyDefaultZweck
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.

Jeder Broker-Actor liest aus seinem eigenen Subtree. Siehe die per-Protokoll-Seiten für die Keys:

SubtreeSeite
io.broker.kafkaKafka
io.broker.mqttMQTT
io.broker.amqpAMQP
io.broker.natsNATS
io.broker.jetstreamNATS JetStream
io.broker.jetstream-key-valueJetStream KV + Object Store
io.broker.jetstream-object-storeJetStream KV + Object Store
io.broker.redis-streamsRedis Streams
io.broker.grpc.{client,server}gRPC
io.broker.websocketWebSocket Client
io.broker.sseSSE
io.broker.tcpTCP
io.broker.udpUDP
gossip-interval = 1s # 1000 ms
unreachable-after = 2.5s # 2500 ms
down-after = 5000ms # explizite ms
gc-cadence = 10m # Minuten
ttl = 24h # Stunden

Erkannte Einheiten: ns, us, ms, s, m, h, d.

max-frame-bytes = 16M # 16 777 216 Bytes
buffer = 64K
heap = 2G

Erkannte Einheiten: B, K, M, G, T — standardmäßig binär (1024).

port = ${?ACTOR_TS_PORT} # nutze env, falls gesetzt, sonst fallback
log-level = ${?LOG_LEVEL} # gleich
api-key = ${API_KEY} # required — Fehler, wenn env nicht gesetzt
fallback-port = ${?ENV_PORT}
fallback-port = ${fallback-port:-2552} # default-if-empty Syntax

Verwende ${?ENV} für optional, ${ENV} für required.

include "shared-cluster.conf" # Fehler: `include` wird nicht unterstützt

Eine 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.

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: abgelehnt
constructor.x = 1 # Fehler: abgelehnt
prototype = 1 # Fehler: abgelehnt
value = ${__proto__} # Fehler: abgelehnt

Ein 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.

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.

1. REFERENCE_CONF ← gebündelte Defaults
2. application.conf (CWD) ← Projekt-Root, auto-geladen
3. Dateipfad aus `configFile`-Setting ← expliziter Override
4. ENV-Var ACTOR_TS_CONFIG ← Pfad oder inline HOCON
5. Konstruktor `config: { ... }` ← Code-Level-Override
6. 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.

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_ENTRIES liegt in InMemoryCacheOptions.ts, DEFAULT_NUM_SHARDS in ShardingOptions.ts.
  • Alles andere — Obergrenzen, Schranken und Timeouts ohne Option dahinter — steht in einer Constants.ts: src/<subsystem>/Constants.ts für ein einzelnes Subsystem, src/util/Constants.ts fü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.

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 als BrokerOptionsError, und nur vorhandene, aber ungültige Werte werden zu OptionsError.
  • 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).