Architecture
Understanding PyStrands' architecture helps you build scalable, resilient real-time applications.
Overview
PyStrands uses a three-tier architecture that separates concerns for scalability and reliability:
┌─────────────────┐ WebSocket ┌─────────────────┐ TCP ┌─────────────────┐
│ │ ←──────────────→ │ │ ←────────────→ │ │
│ Web/Browser │ │ Go Broker │ │ Python Backend │
│ Clients │ │ (stateful) │ │ (your code) │
│ │ │ │ │ │
└─────────────────┘ │ ┌───────────┐ │ └─────────────────┘
│ │ Queue │ │ ┌─────────────────┐
│ │ [msg1] │ │ ←────────────→ │ Python Backend │
│ │ [msg2] │ │ │ (replica 2) │
│ │ [msg3] │ │ └─────────────────┘
│ └───────────┘ │
└─────────────────┘
Components
1. WebSocket Clients
Clients connect to the Go broker via WebSocket. They can be: - Browser applications (JavaScript WebSocket API) - Mobile apps - IoT devices - Any WebSocket-capable client
Clients are identified by a unique client_id and can be organized into rooms for targeted messaging.
2. Go Broker
The Go broker is the heart of PyStrands. It handles:
- WebSocket connections — Manages client connections and disconnections
- Message routing — Routes messages between clients and Python backends
- Room management — Maintains room state and membership
- Message queuing — Buffers messages when backends are unavailable
- Load balancing — Distributes work across multiple Python backends
Stateless Design
The Go broker is designed to be stateless relative to Python backends. Multiple broker instances can run behind a load balancer for horizontal scaling.
3. Python Backends
Your Python code connects to the Go broker via TCP and handles business logic:
- Connection validation — Accept or reject incoming WebSocket connections
- Message processing — Handle incoming messages and send responses
- State management — Maintain application state (database, cache, etc.)
Python backends can scale horizontally — add more instances to handle increased load.
Message Flow
Connection Flow
1. Client → Go Broker: WebSocket upgrade request
2. Go Broker → Python Backend: connection_request message
3. Python Backend → Go Broker: accept/reject response
4. Go Broker → Client: WebSocket established (or rejected)
5. Go Broker → Python Backend: new_connection notification
Message Flow
1. Client A → Go Broker: "Hello!"
2. Go Broker → Python Backend: new_message with context
3. Python Backend processes message
4. Python Backend → Go Broker: send_room_message(room_id, "Echo: Hello!")
5. Go Broker → All clients in room: "Echo: Hello!"
Scaling
Horizontal Scaling Python Backends
Add more Python backend instances to handle increased load:
┌─────────────────┐
│ Load Balancer │
│ (WebSocket) │
└────────┬────────┘
│
┌────────▼────────┐
│ Go Broker 1 │
│ Go Broker 2 │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌──────▼──────┐ ┌─────▼──────┐ ┌─────▼──────┐
│ Python │ │ Python │ │ Python │
│ Backend 1 │ │ Backend 2 │ │ Backend 3 │
└─────────────┘ └────────────┘ └────────────┘
The Go broker load-balances connection requests and messages across all connected Python backends.
Message Queuing
When all Python backends disconnect, the Go broker can queue messages (if --queue-size > 0):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │ ──────→ │ Go Broker │ │ No Python │
│ (sending) │ │ (queues │ X │ backends │
│ │ │ message) │ │ connected │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
(Python backend reconnects)
│
▼
┌─────────────┐
│ Go Broker │ ──────→ │ Python │
│ (flushes │ │ Backend │
│ queue) │ │ (receives │
└─────────────┘ │ queued msg) │
└─────────────┘
Queue Limitations
The message queue is stored in memory. If the queue fills up or the broker restarts, queued messages are lost. For guaranteed delivery, implement application-level acknowledgments.
Protocol
TCP Protocol (Python ↔ Go)
Messages between Python backends and the Go broker use JSON over TCP, delimited by newlines:
{
"action": "message_to_room",
"request_id": "uuid-v4-string",
"params": {
"room_id": "room1",
"message": "Hello, room!"
}
}
Actions
From Python to Go:
| Action | Description |
|---|---|
broadcast |
Send message to all connected clients |
message_to_room |
Send message to all clients in a room |
message_to_connection |
Send message to a specific client |
response |
Response to a connection request |
From Go to Python:
| Action | Description |
|---|---|
connection_request |
New WebSocket client wants to connect |
new_connection |
Client successfully connected |
new_message |
Client sent a message |
disconnected |
Client disconnected |
error |
An error occurred |
heartbeat |
Keep-alive ping from server |
Deployment Patterns
Single Server
For development or small deployments:
┌─────────────────────────────────────┐
│ Single Server │
│ ┌─────────┐ ┌───────────────┐ │
│ │ Go │←──→│ Python │ │
│ │ Broker │ │ Backend │ │
│ └────┬────┘ └───────────────┘ │
│ │ │
│ [WebSocket Port 8080] │
└───────┼─────────────────────────────┘
│
[Browser Clients]
Multi-Backend Production
For production with high availability:
┌─────────────────┐
│ Load Balancer │ (nginx, HAProxy, cloud LB)
│ (WebSocket) │
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│ Go │ │ Go │
│Broker1│ │Broker2│
└───┬───┘ └───┬───┘
│ │
└────┬────┘
│
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│Python │ │Python │
│Backend│ │Backend│
│ 1 │ │ 2 │
└───────┘ └───────┘
Sticky Sessions
WebSocket connections require sticky sessions when using multiple Go brokers. Configure your load balancer accordingly.