TUTORIAL 10

Technical Spike

The team needs real-time notifications. Should you use WebSockets, SSE, or polling? Investigate before committing architecture.

โฑ ~15 min ยท Hands-on

/agile-explore-brainstorm /agile-explore-spike /agile-memory-learn /agile-story-create
Scenario

Users have been requesting real-time notifications โ€” order status updates, new messages, system alerts. The team doesn't know which technology to use. Before committing to an architecture, you need to investigate the options with a timeboxed technical spike.

Step 1: Define the Spike Question

A spike starts with a clear, answerable question. Vague spikes waste time.

Spike Definition

Spike: Real-Time Notification Technology
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
Question: Which real-time technology best fits
our notification requirements?

Requirements:
  - Low latency (under 2 seconds)
  - Moderate scale (10K concurrent users)
  - Server-to-client push (bidirectional NOT required)
  - Browser-native support preferred
  - Must work behind corporate proxies

Non-goals:
  - Chat or collaborative editing
  - Real-time data sync (CRDT/OT)

Step 2: Set a Timebox

Spikes are timeboxed to prevent rabbit holes. When the timebox expires, you decide with what you have โ€” even if the answer is "we need another spike."

Timebox: 4 Hours

This spike is limited to 4 hours. After 4 hours, the team must make a decision or explicitly schedule a follow-up spike. No open-ended research.

Step 3: Brainstorm Approaches

Explore all reasonable options before narrowing down. Don't pick the first idea โ€” compare systematically.

/agile-explore-brainstorm

Option 1: WebSockets

WebSockets
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
  Protocol:    Full-duplex TCP over HTTP upgrade
  Direction:   Bidirectional
  Complexity:  High โ€” connection management, heartbeats,
               reconnection logic, load balancer config
  Scaling:     Requires sticky sessions or pub/sub adapter
  Proxy:       Some corporate proxies block WS upgrades
  Verdict:     Overkill โ€” we don't need bidirectional

Option 2: Server-Sent Events (SSE)

Server-Sent Events (SSE)
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
  Protocol:    HTTP long-lived connection, text/event-stream
  Direction:   Server โ†’ Client only
  Complexity:  Low โ€” built on standard HTTP, native API
  Scaling:     Standard HTTP load balancing works
  Proxy:       Works through all HTTP proxies
  Browser:     Native EventSource API, auto-reconnect built-in
  Verdict:     Perfect fit โ€” server push, simple, reliable

Option 3: Polling

HTTP Polling
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
  Protocol:    Standard HTTP GET on interval
  Direction:   Client pulls from server
  Complexity:  Lowest โ€” just regular API calls
  Scaling:     Wasteful โ€” N users ร— interval = heavy load
  Latency:     Up to 1 full interval delay
  Verdict:     Too wasteful at 10K users, poor latency

Option 4: Managed Service (Firebase / Pusher)

Managed Service (Firebase / Pusher)
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
  Protocol:    Vendor SDK over WebSockets
  Direction:   Bidirectional (via vendor)
  Complexity:  Low to implement, high vendor lock-in
  Scaling:     Handled by vendor
  Cost:        $50-500/month at our scale
  Verdict:     Adds dependency and ongoing cost for simple need

Score each option against requirements:

Comparison Matrix

                    Feasibility  Complexity  Fit   Score
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
  WebSockets           9/10        3/10      5/10   17
  SSE                  9/10        9/10      10/10  28  โ† winner
  Polling              10/10       10/10     4/10   24
  Firebase/Pusher      8/10        8/10      6/10   22
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
  Fit = how well it matches our specific requirements
  Higher is better for all columns

Step 4: Pick Top 2 for Prototyping

Brainstorming narrows the field. Now build throwaway prototypes for the top contenders to validate assumptions with real code.

Top 2 Candidates

SSE (highest score, best fit) and WebSockets (industry standard, worth comparing). Polling and managed services are eliminated.

Step 5: Build Throwaway Prototypes

/agile-explore-spike

SSE Prototype โ€” 30 Lines

// spike/sse-server.js โ€” THROWAWAY CODE
const express = require("express");
const app = express();

app.get("/notifications", (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  // Send a notification every 3 seconds
  const interval = setInterval(() => {
    const notification = {
      type: "order_update",
      message: "Order #1234 shipped",
      timestamp: new Date().toISOString()
    };
    res.write(`data: ${JSON.stringify(notification)}\n\n`);
  }, 3000);

  req.on("close", () => clearInterval(interval));
});

app.listen(3001);

// spike/sse-client.js โ€” THROWAWAY CODE
const source = new EventSource("/notifications");
source.onmessage = (event) => {
  const notification = JSON.parse(event.data);
  showToast(notification.message);
};
// Auto-reconnect is built into EventSource โ€” no extra code needed
Result: Works immediately. Auto-reconnect built in.
Time to working prototype: 15 minutes.
Lines of code: 30

WebSocket Prototype โ€” 80 Lines

// spike/ws-server.js โ€” THROWAWAY CODE
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 3002 });

const clients = new Set();

wss.on("connection", (ws) => {
  clients.add(ws);
  ws.on("close", () => clients.delete(ws));

  // Heartbeat to detect dead connections
  ws.isAlive = true;
  ws.on("pong", () => { ws.isAlive = true; });
});

// Heartbeat interval
setInterval(() => {
  wss.clients.forEach((ws) => {
    if (!ws.isAlive) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

// Broadcast notification
setInterval(() => {
  const notification = JSON.stringify({
    type: "order_update",
    message: "Order #1234 shipped",
    timestamp: new Date().toISOString()
  });
  clients.forEach((ws) => {
    if (ws.readyState === WebSocket.OPEN) ws.send(notification);
  });
}, 3000);

// spike/ws-client.js โ€” THROWAWAY CODE
let ws;
function connect() {
  ws = new WebSocket("ws://localhost:3002");
  ws.onmessage = (event) => {
    const notification = JSON.parse(event.data);
    showToast(notification.message);
  };
  ws.onclose = () => setTimeout(connect, 1000); // manual reconnect
}
connect();
Result: Works, but adds complexity for no benefit.
  - Manual heartbeat logic required
  - Manual reconnection required
  - Load balancer needs sticky session config
  - Bidirectional capability unused
Time to working prototype: 40 minutes.
Lines of code: 80

Step 6: Document Findings

The spike produces a clear recommendation with evidence, not just an opinion.

Spike Conclusion

Spike Result: SSE Wins
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

Recommendation: Use Server-Sent Events (SSE)

Evidence:
  1. SSE prototype: 30 lines, 15 min, works immediately
  2. WebSocket prototype: 80 lines, 40 min, added complexity
  3. SSE auto-reconnect is built into the browser API
  4. SSE works through all HTTP proxies (WebSockets may not)
  5. SSE uses standard HTTP โ€” no special load balancer config
  6. We don't need bidirectional communication

Trade-off accepted:
  If we later need bidirectional (e.g., chat), we would
  need to add WebSockets alongside SSE. This is acceptable
  because chat is not on the roadmap.

Time spent: 2.5 hours of 4-hour timebox
Spike Code Is THROWAWAY

Never merge prototype code to develop. Spike code cuts corners deliberately โ€” no error handling, no tests, no security. The production implementation will be written from scratch using /agile-code-tdd with proper architecture.

Step 7: Record the Decision

Capture the decision as an Architecture Decision Record (ADR) so future team members understand why SSE was chosen.

/agile-memory-learn

ADR-007: Use SSE for Real-Time Notifications

ADR-007: Use SSE for Real-Time Notifications
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
Status:   Accepted
Date:     2026-04-04
Context:  Need server-to-client push for notifications
Decision: Use Server-Sent Events (SSE) over WebSockets
Reason:   Simpler, fits requirements (no bidirectional needed),
          native browser support, works through proxies

Alternatives considered:
  - WebSockets: rejected (unnecessary complexity)
  - Polling: rejected (wasteful, poor latency)
  - Firebase: rejected (vendor lock-in, cost)

Consequences:
  - If chat feature is added later, will need WebSockets too
  - SSE limited to ~6 connections per domain in HTTP/1.1
    (not a concern โ€” we use HTTP/2)

โ†’ Saved to: .memory/episodic/decisions.md

Step 8: Create Implementation Stories

The spike is done. Now turn the decision into actionable stories for the next sprint.

/agile-story-create

Stories Created from Spike

Stories for Sprint Backlog:
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

๐Ÿ“‹ NOTIF-001: SSE notification endpoint
   As a user, I want to receive real-time notifications
   so that I see order updates without refreshing.
   AC: SSE endpoint at /api/notifications/stream,
       sends JSON events, auto-reconnects on disconnect.

๐Ÿ“‹ NOTIF-002: Notification UI component
   As a user, I want to see a notification bell with
   a badge count so that I know when new events arrive.
   AC: Bell icon in header, badge shows unread count,
       dropdown shows last 10 notifications.

๐Ÿ“‹ NOTIF-003: Notification preferences
   As a user, I want to choose which notifications I
   receive so that I'm not overwhelmed by alerts.
   AC: Settings page with toggles per notification type,
       preferences saved to user profile.

Spike Flow

โ“
Define Question clear + answerable
โฑ๏ธ
Timebox 4 hours max
๐Ÿง 
/agile-explore-brainstorm all options
๐Ÿ”ฌ
/agile-explore-spike build prototypes
๐Ÿ“
/agile-memory-learn record ADR
๐Ÿ“‹
/agile-story-create plan work

What You Practiced

Knowledge Check

What does a spike produce?