Stack: DRF + ASGI · Project: TicketFlow Status: Published Prerequisite: Lesson 15 — Validation and OpenAPI
Objectives
- Compare REST/gRPC/GraphQL with the judgement of who consumes what (and each one's cost).
- Choose between WebSockets and SSE for TicketFlow's live availability.
- Implement an SSE endpoint with Django ASGI and understand its concurrency implication (Lesson 03 returns).
1. The protocol map
| REST/JSON | gRPC | GraphQL | WS/SSE | |
|---|---|---|---|---|
| Model | resources | typed RPC | queryable graph | server push |
| Contract | OpenAPI | .proto (binary) | schema SDL | event |
| Typical client | everyone | micro↔micro | rich frontend | real time |
| HTTP cache | yes (12) | no (HTTP/2 but binary bodies) | partial (POST) | no |
| Learning curve | low | medium (tooling) | medium-high (N+1, perms) | medium |
Choice criterion: REST for the public API (a universe of clients, caching, curl debugging); gRPC internal (your own services: strict binary contracts, streaming, performance — when there are real microservices, Lesson 53); GraphQL only if the frontend needs to combine many resources with varying shapes and the team pays its complexity (N+1 in resolvers, per-field permissions); WS/SSE to push events.
GraphQL's trap: "one single endpoint" sounds like freedom and is moving the N+1 to the server (dataloader mandatory) and access control to every field (21). It's not bad: it is a continuous cost, not a sprint.
2. The TicketFlow case: live availability
The buyer stares at the seat map; seats sell under their feet. Options:
- Polling:
GET /availabilityevery 3s — simple, HTTP-cacheable (12), but 1000 users watching = 333 rps of identical queries (bearable with the Redis cache). - SSE (Server-Sent Events): the server pushes "seat sold" over a one-way HTTP stream — one open connection per user, push at the moment. Native browser reconnect (EventSource), plain text, proxy-friendly.
- WebSocket: a bidirectional channel — if the user also emits (support chat, auctions), yes; for "the server notifies", it's overkill.
TicketFlow's decision: SSE for availability (one-way, simple, HTTP/2 friendly) + cached polling as fallback. WebSocket only if real bidirectionality appears (auctions).
3. SSE with Django ASGI (the code that opens the door)
# events/sse.py (consumer on ASGI — Django with Uvicorn, Lesson 03)
async def availability_stream(request, event_uuid):
event = await get_event(event_uuid) # sync_to_async
channel = f"avail:{event.id}"
pubsub = redis_client.pubsub()
await pubsub.subscribe(channel)
async for message in pubsub.listen():
if message["type"] == "message":
yield {"event": "availability", "data": message["data"]}Costs you must manage: every stream is one open connection (1000 users = 1000 connections — Uvicorn/ASGI handles them because it's I/O, Lesson 03); keepalive every ~15s to cross proxies (Nginx: proxy_buffering off on the SSE route); events and reconnection with Last-Event-ID so sales aren't lost between reconnects; and the pub/sub (Redis publish from the service confirming the reservation → every stream of that event receives the push).
4. When none of this is worth it
If availability changes 10 times per hour and the map refreshes on entry, cached polling is enough and costs zero new infrastructure. Every push technology adds: persistent connections (memory/LB limits), reconnects (duplicates), and a new failure mode. Push is justified when the business demands the instant (auctions, re-sell ticket markets, collaboration), not because "real-time is nice to have".
Self-assessment
- Why does REST remain the choice for a public API even though gRPC is "faster"?
- What does GraphQL add as a continuous cost, and which case justifies it?
- SSE vs WebSocket for availability: what decides the choice? When does WS win?
- Why does SSE need
proxy_buffering offon Nginx and keepalive? What happens without them? - 1000 open SSE streams: which resource runs out first and how do you size for it (connect with 03/05)?
Continue with the exercises. The solutions only after trying it yourself.