Zum Inhalt springen
Deutsch

Chat-Sample

Das Chat-Sample ist eine komplette Demo-App, die zeigt, wie die Teile des Frameworks zusammenspielen:

  • Cluster aus 3 Nodes (über Docker Compose).
  • Sharded User-Session-Actors - einer pro eingeloggtem User.
  • DistributedPubSub für Cross-Node-Chatroom-Broadcasts.
  • PersistentActor für die Chatroom-History.
  • HTTP + WebSocket für das Client-Interface.

Zu finden unter examples/chat/ im Repo.

3-Node-Cluster

join / send

publish

deliver

HTTP-Clients

WebSocket-Clients

HTTP-Server

WS-Server

Sharded UserSession-Actors

einer pro eingeloggtem User

hält Socket + State

ChatRoom-Actors (PersistentActor)

einer pro Room

persistiert Nachrichten-History

DistributedPubSub

Topic pro Room

Rooms publishen; Sessions abonnieren

Der vollständige Pfad eines “User sendet Nachricht”-Flows:

1. Der WS-Client des Users sendet "send-message" an seinen HTTP-Server.
2. Der WS-Handler leitet an den UserSession-Actor des Users weiter (sharded).
3. UserSession fragt den zuständigen ChatRoom-Actor (sharded nach Room-ID).
4. ChatRoom persistiert die Nachricht via PersistentActor.persist().
5. ChatRoom publisht auf DistributedPubSub auf das Topic "room.<roomId>".
6. Jede UserSession, die dieses Topic abonniert hat, empfängt die Nachricht.
7. UserSessions pushen an ihre jeweiligen WS-Clients.

Cluster + Persistenz + PubSub + WebSocket - alle zusammen am Werk.

Terminal-Fenster
# Repo klonen:
git clone https://github.com/pathosDev/actor-ts.git
cd actor-ts/examples/chat
# Alles starten (Cluster + Cassandra + WS):
docker compose up -d
# Cluster prüfen:
curl http://localhost:8551/cluster/members
# → 3 members "up"
# Chat-UI öffnen:
open http://localhost:3000

Das Docker-Compose-Setup startet:

  • 3 actor-ts-Pods.
  • 1 Cassandra-Node (geteiltes Journal für persistente Rooms).
  • 1 Nginx vor den HTTP+WS-Endpoints.
const sessionRegion = cluster.sharding.start<SessionMsg>({
typeName: 'session',
entityProps: Props.create(() => new UserSessionActor()),
extractEntityId: (msg) => msg.userId,
rememberEntities: true,
});

Ein Actor pro eingeloggtem User, verteilt über die Nodes. rememberEntities erhält die Registry über Failover hinweg.

class ChatRoomActor extends PersistentActor<RoomCmd, RoomEvent, RoomState> {
readonly persistenceId = `room-${this.roomId}`;
// ... onCommand persistiert; onEvent aktualisiert State ...
}

Jeder Room-Actor zeichnet jede Nachricht auf; Recovery spielt sie zurück.

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

class UserSessionActor extends Actor<SessionMsg> {
private ws: WebSocket | null = null;
override onReceive(msg: SessionMsg): void {
if (msg.kind === 'connect-ws') this.ws = msg.socket;
if (msg.kind === 'inbound') this.ws?.send(JSON.stringify(msg.payload));
}
}

Jede Session hält den WebSocket ihres Users; Sends pushen direkt zum Client.

  • Sharded Daemon Processes - das Chat-Sample braucht keine fixen Background-Worker.
  • DistributedData CRDTs - Chat-Daten gehen durch PersistentActor, nicht DD.
  • Replicated Event Sourcing - ein Single-Writer pro Room reicht aus.

Dafür siehe die eigenständigen Snippets oder das Voice-Sample.

examples/chat/
├── docker-compose.yml
├── README.md
├── package.json
├── src/
│ ├── main.ts # Einstieg: Cluster-Join + HTTP/WS-Bind
│ ├── actors/
│ │ ├── UserSessionActor.ts
│ │ └── ChatRoomActor.ts
│ ├── messages.ts # geteilte Nachrichten-Typen
│ └── handlers/
│ ├── httpRoutes.ts
│ └── wsHandlers.ts
└── ui/ # minimale HTML/JS-Chat-UI

~500 Zeilen TypeScript insgesamt. Gute Größe, um es end-to-end zu lesen.