Chat-Sample
Das Chat-Sample ist eine komplette Demo-App, die zeigt, wie die Teile des Frameworks zusammenspielen:
- TCP-Cluster aus 3 Bun-Prozessen, per Gossip verbunden.
- Sharded Chatroom-Actors - ein
ChatRoomActorpro Room, über den Cluster als 16 Shards verteilt. - DistributedPubSub für Cross-Node-Chatroom-Broadcasts.
- PersistentActor für die Chatroom-History (SQLite-Journal + Snapshots).
- DistributedData für Presence, das Laufzeit-Room-Directory und Read-Receipts.
- ClusterSingleton als HTTP-Eingangstür - ein Node bindet
:8080; bei Failover übernimmt ein überlebender Node den Bind. - WebSocket-only-Client-Protokoll - ein einziger
/ws-Endpoint trägt Login, Rooms, History, Presence und Nachrichten.
Zu finden unter examples/chat/
im Repo.
Architektur
Abschnitt betitelt „Architektur“Der vollständige Pfad eines “User sendet Nachricht”-Flows:
1. Der WS-Client sendet einen "send"-Frame über /ws an den Ingress-Singleton.2. Der Ingress routet ihn an den UserSession-Actor dieser Verbindung (einer pro Socket).3. UserSession informiert die zuständige ChatRoom-Entity (sharded nach Room-Name).4. ChatRoom persistiert die Nachricht via PersistentActor.persist().5. ChatRoom publisht auf DistributedPubSub auf das Topic des Rooms.6. Jede UserSession, die dieses Topic abonniert hat, empfängt die Nachricht.7. Jede UserSession pusht sie an ihren eigenen WS-Client.Cluster + Persistenz + PubSub + WebSocket - alle zusammen am Werk.
Ausführen
Abschnitt betitelt „Ausführen“# Repo klonen und Dev-Deps installieren:git clone https://github.com/pathosDev/actor-ts.gitcd actor-tsbun install
# 3-Node-Cluster starten - drei Terminals, gleicher Befehl, keine Flags:bun examples/chat/backend/main.tsbun examples/chat/backend/main.tsbun examples/chat/backend/main.ts
# Chat-UI öffnen (eine URL, egal welcher Node den Singleton hält):open http://localhost:8080/Jeder Node führt dasselbe Binary aus. Sie finden einander,
indem sie den Cluster-Port-Bereich ab 2551 scannen, sodass
sich die drei Terminals ohne konfigurierte Seeds auf
2551 / 2552 / 2553 einpendeln. Der Cluster wählt dann einen
Node, der den HTTP-Ingress-ClusterSingleton betreibt; dieser
bindet :8080 und serviert den Frontend-Selector sowie den
/ws-Endpoint. Die Persistenz ist ein lokales SQLite-Journal +
Snapshot-Store unter ./data/ - keine externe Datenbank.
Beende den Node, der den Singleton hält, und ein überlebender
Node bindet :8080 innerhalb weniger Sekunden neu; die
persistierte History bleibt erhalten.
Demonstrierte Schlüssel-Patterns
Abschnitt betitelt „Demonstrierte Schlüssel-Patterns“Sharded Chatrooms
Abschnitt betitelt „Sharded Chatrooms“const chatRoomRegion = cluster.sharding.start('ChatRoom', ChatRoomActor, StartShardingOptions.create<ChatRoomCommand>() .withExtractEntityId((message) => message.room) .withNumShards(16));Ein ChatRoomActor pro Room, über den Cluster als 16 Shards
verteilt - beende einen beliebigen Node, und die überlebenden
Nodes übernehmen dessen Rooms. Direct-Message-Channels nutzen
dasselbe Pattern in einer separaten Region, gekeyt auf die
kanonische DM-Pair-ID.
PersistentActor für Rooms
Abschnitt betitelt „PersistentActor für Rooms“class ChatRoomActor extends PersistentActor<ChatRoomCommand, ChatEvent, ChatState> { readonly persistenceId = `room-${this.roomName}`; // ... onCommand persistiert; onEvent aktualisiert State ...}Jeder Room-Actor zeichnet jede Nachricht auf; Recovery spielt sie zurück.
Distributed Pub/Sub für Fan-out
Abschnitt betitelt „Distributed Pub/Sub für Fan-out“// ChatRoom publisht nach dem Persistieren:ps.mediator.tell(new Publish(`room.${roomId}`, message));
// UserSession abonniert, wenn der User einem Room beitritt:ps.mediator.tell(new Subscribe(`room.${roomId}`, this.self));Sessions auf beliebigen Nodes empfangen Room-Nachrichten, unabhängig davon, auf welchem Node der ChatRoom publisht.
WebSocket pro User
Abschnitt betitelt „WebSocket pro User“class UserSessionActor extends Actor<SessionMessage> { private ws: WebSocket | null = null;
override onReceive(message: SessionMessage): void { match(message) .with({ kind: 'connect-ws' }, (m) => this.onConnectWs(m)) .with({ kind: 'inbound' }, (m) => this.onInbound(m)) .exhaustive(); }
private onConnectWs(message: ConnectWsMessage): void { this.ws = message.socket; }
private onInbound(message: InboundMessage): void { this.ws?.send(JSON.stringify(message.payload)); }}Jede Session hält den WebSocket ihres Users; Sends pushen direkt zum Client.
Was es nicht demonstriert
Abschnitt betitelt „Was es nicht demonstriert“- Sharded Daemon Processes - das Chat-Sample braucht keine fixen Background-Worker.
- Replicated Event Sourcing - ein Single-Writer pro Room reicht aus.
Dafür siehe die eigenständigen Snippets oder das Voice-Sample.
Dateistruktur
Abschnitt betitelt „Dateistruktur“examples/chat/├── README.md├── application.conf # HOCON: Log-Level, Gossip-Kadenz├── data/ # SQLite-Journal + Snapshots (gitignored)├── backend/│ ├── main.ts # Einstieg: nur Wiring (Cluster, Persistenz, Sharding, Singleton)│ ├── config.ts # CLI-Argument-Parsing│ ├── routes.ts # HTTP-DSL-Route (Frontend-Selector)│ ├── auth/ # scrypt-Passwortprüfung + HMAC-Session-Tokens│ ├── discovery/ # Same-Host-Port-Scan-Seed-Provider│ └── actors/│ ├── ChatRoomActor.ts # sharded PersistentActor (pro Room)│ ├── ChatRoomDirectoryActor.ts # DistributedData ORSet (Laufzeit-Rooms)│ ├── DirectMessageChannelActor.ts # sharded PersistentActor (pro DM-Paar)│ ├── UserSessionActor.ts # Session pro WS-Verbindung│ ├── OnlineUsersActor.ts # DistributedData ORSet (Presence)│ ├── ReadReceiptsActor.ts # DistributedData LWWMap (Read-Pointer)│ ├── HttpIngressActor.ts # ClusterSingleton: hält den :8080-Bind│ └── WebsocketIngressActor.ts # WS-Plumbing pro Verbindung├── shared/│ ├── protocol.ts # geteilte WS-Nachrichten-Typen│ ├── rooms.ts # Default-Room-Liste│ ├── users.ts # Test-Credentials│ └── directMessage.ts # Kanonisierung der DM-Pair-ID├── static/ # gebaute Frontend-Assets (@fastify/static)├── frontend-{plain,angular,react,next,svelte,lit}/ # sechs UI-Varianten├── smoke-test.ts # Single-Node-Messaging-Round-Trip└── failover-test.ts # HTTP-Singleton-FailoverJeder Actor lebt in seiner eigenen Datei unter
backend/actors/; main.ts ist reines Wiring. Sechs Frontends
teilen sich ein WebSocket-Protokoll, sodass du sie direkt
nebeneinander vergleichen kannst.
Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- Voice-Sample - Broker-Integration + Projektionen.
- Sharding-Übersicht - das Actor-pro-Entity-Pattern.
- DistributedPubSub - Cluster-PubSub.
- PersistentActor - Event-Sourced Rooms.
