SiamCafe.net Blog
Technology

Qwik Resumability Pub Sub Architecture

qwik resumability pub sub architecture
Qwik Resumability Pub Sub Architecture | SiamCafe Blog
2025-07-01· อ. บอม — SiamCafe.net· 11,003 คำ

Qwik Pub/Sub

Qwik Resumability Pub/Sub Architecture Event-driven WebSocket SSE Real-time Redis NATS Lazy Loading Fine-grained TTI

TransportDirectionUse CaseComplexity
SSE (Server-Sent Events)Server → ClientNotification, Live Feed, Priceง่าย
WebSocketBidirectionalChat, Collaboration, Gamingปานกลาง
Long PollingClient → ServerLegacy Fallbackง่าย (แต่ Inefficient)
WebTransportBidirectionalLow Latency Gamingสูง (ใหม่)

Qwik Component Design

// === Qwik Pub/Sub Component ===

// import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
//
// interface Message {
//   id: string;
//   topic: string;
//   data: any;
//   timestamp: number;
// }
//
// export const LiveFeed = component$(() => {
//   const messages = useSignal([]);
//   const connected = useSignal(false);
//
//   useVisibleTask$(({ cleanup }) => {
//     // SSE Connection - Only runs on client, only when visible
//     const eventSource = new EventSource('/api/subscribe?topic=prices');
//
//     eventSource.onopen = () => {
//       connected.value = true;
//     };
//
//     eventSource.onmessage = (event) => {
//       const msg: Message = JSON.parse(event.data);
//       messages.value = [msg, ...messages.value.slice(0, 49)];
//     };
//
//     eventSource.onerror = () => {
//       connected.value = false;
//       // Auto-reconnect built into EventSource
//     };
//
//     cleanup(() => {
//       eventSource.close();
//       connected.value = false;
//     });
//   });
//
//   return (
//     
// {connected.value ? 'Connected' : 'Disconnected'} // {messages.value.map((msg) => ( //
{msg.topic}: {JSON.stringify(msg.data)}
// ))} //
// ); // }); // === Benefits of Resumability + Pub/Sub === // 1. Zero JS until user scrolls to LiveFeed component (Lazy) // 2. SSE connection only starts when component is visible // 3. cleanup() automatically closes connection on unmount // 4. useSignal provides fine-grained reactivity (no re-render entire tree) from dataclasses import dataclass @dataclass class QwikPattern: pattern: str api: str when_runs: str use_case: str patterns = [ QwikPattern("Server Data Load", "routeLoader$()", "Server-side (SSR)", "Initial Data, SEO Content, DB Query"), QwikPattern("Client Subscription", "useVisibleTask$()", "Client-side เมื่อ Component Visible", "WebSocket SSE Connection, Timer"), QwikPattern("Reactive State", "useSignal() / useStore()", "ทั้ง Server และ Client", "UI State, Message List, Connection Status"), QwikPattern("Server Action", "server$()", "Server-side (เรียกจาก Client)", "Publish Message, DB Write, API Call"), QwikPattern("Form Mutation", "routeAction$()", "Server-side (จาก Form Submit)", "Send Message, Create Post, Update Settings"), ] print("=== Qwik Patterns ===") for p in patterns: print(f" [{p.pattern}] API: {p.api}") print(f" Runs: {p.when_runs}") print(f" Use: {p.use_case}")

Backend Pub/Sub

# === Backend Pub/Sub with Redis + SSE ===

# Server-side SSE Endpoint (Node.js / Qwik Server)
# export const onGet: RequestHandler = async ({ send, headers }) => {
#   headers.set('Content-Type', 'text/event-stream');
#   headers.set('Cache-Control', 'no-cache');
#   headers.set('Connection', 'keep-alive');
#
#   const redis = new Redis();
#   await redis.subscribe('prices');
#
#   redis.on('message', (channel, message) => {
#     send(`data: \n\n`);
#   });
# };

# Publish from another service
# import redis
# r = redis.Redis()
# r.publish('prices', json.dumps({
#     'id': str(uuid4()),
#     'topic': 'BTC/USD',
#     'data': {'price': 67500.50, 'change': 2.3},
#     'timestamp': time.time()
# }))

@dataclass
class PubSubBackend:
    broker: str
    protocol: str
    throughput: str
    persistence: str
    best_for: str

backends = [
    PubSubBackend("Redis Pub/Sub",
        "TCP + RESP Protocol",
        "100K+ msg/s",
        "No (Fire & Forget)",
        "Real-time ไม่ต้องเก็บ History"),
    PubSubBackend("Redis Streams",
        "TCP + RESP Protocol",
        "100K+ msg/s",
        "Yes (Append-only Log)",
        "Real-time + History + Consumer Group"),
    PubSubBackend("NATS",
        "TCP + NATS Protocol",
        "10M+ msg/s",
        "Yes (JetStream)",
        "High Throughput Microservices"),
    PubSubBackend("Kafka",
        "TCP + Kafka Protocol",
        "1M+ msg/s",
        "Yes (Durable Log)",
        "Event Sourcing Big Data Pipeline"),
    PubSubBackend("RabbitMQ",
        "AMQP",
        "50K+ msg/s",
        "Yes (Queue)",
        "Task Queue Complex Routing"),
]

print("=== Pub/Sub Backends ===")
for b in backends:
    print(f"\n  [{b.broker}] Protocol: {b.protocol}")
    print(f"    Throughput: {b.throughput}")
    print(f"    Persistence: {b.persistence}")
    print(f"    Best for: {b.best_for}")

Production Patterns

# === Production Patterns ===

@dataclass
class ProdPattern:
    pattern: str
    problem: str
    solution: str
    implementation: str

prod_patterns = [
    ProdPattern("Reconnection with Backoff",
        "Connection หลุดจาก Network Issue",
        "Exponential Backoff: 1s 2s 4s 8s max 30s",
        "SSE มี Auto-reconnect WS ต้องเขียนเอง"),
    ProdPattern("Authentication",
        "Unauthorized Subscribe",
        "ส่ง JWT Token ใน URL param หรือ First Message",
        "EventSource('/api/sub?token=xxx') หรือ WS onopen"),
    ProdPattern("Topic-based Filtering",
        "ไม่อยากรับทุก Message",
        "Subscribe เฉพาะ Topic ที่สนใจ",
        "redis.subscribe('prices:BTC', 'prices:ETH')"),
    ProdPattern("Heartbeat / Keep-alive",
        "Connection timeout จาก Proxy/LB",
        "ส่ง Heartbeat ทุก 30 วินาที",
        "setInterval(() => send(':heartbeat'), 30000)"),
    ProdPattern("Message Deduplication",
        "ได้ Message ซ้ำจาก Reconnect",
        "ใช้ Message ID + Last Event ID",
        "EventSource lastEventId, Server filter dup"),
    ProdPattern("Cleanup on Unmount",
        "Memory Leak จาก Connection ค้าง",
        "Close Connection เมื่อ Component Unmount",
        "useVisibleTask$ cleanup() callback"),
]

print("=== Production Patterns ===")
for p in prod_patterns:
    print(f"  [{p.pattern}]")
    print(f"    Problem: {p.problem}")
    print(f"    Solution: {p.solution}")
    print(f"    Impl: {p.implementation}")

เคล็ดลับ

Qwik Resumability คืออะไร

Web Framework Resumability แทน Hydration Zero JS TTI เร็ว Lazy Loading Fine-grained $ sign component$ useTask$ onClick$

Pub/Sub Pattern คืออะไร

Publisher Subscriber Topic Broker Decoupled Redis NATS Kafka RabbitMQ Scalable Real-time WebSocket SSE Notification Chat Dashboard

ใช้กับ Qwik อย่างไร

useVisibleTask$ SSE WebSocket useSignal useStore server$ routeLoader$ routeAction$ EventSource cleanup Reactive State Real-time

Production Best Practices คืออะไร

SSE ง่ายกว่า WS Reconnection Backoff Auth JWT Token Topic Filter Heartbeat Dedup Message ID Cleanup Unmount Rate Limiting Dead Letter

สรุป

Qwik Resumability Pub/Sub SSE WebSocket Redis NATS useVisibleTask$ useSignal Lazy Loading Real-time Reconnection Heartbeat Production

📖 บทความที่เกี่ยวข้อง

Redis Pub Sub SSL TLS Certificateอ่านบทความ → Qwik Resumability Tech Conference 2026อ่านบทความ → Qwik Resumability Service Mesh Setupอ่านบทความ → Radix UI Primitives Pub Sub Architectureอ่านบทความ → Solid.js Signals Pub Sub Architectureอ่านบทความ →

📚 ดูบทความทั้งหมด →