7 lessons

Real-Time & Async Operations

When request/response stops fitting: WebSocket message contracts, SSE, streaming, the async job pattern for long-running work, and how completion actually reaches the client.

RequirementConsumersResource ModelStyleContractValidationAuthorizationErrorsIdempotencyPaginationVersioningObservabilityEvolutionTrade-offs

Every lesson below names the consumers, the design question and the guarantee before recommending anything. Recommendations come with what they cost, when not to use them, and how they evolve.

WebSocket Message Contracts

A WebSocket gives you a pipe, not a protocol. Everything HTTP provided for free — operations, status codes, request/response pairing — you must now design: typed message envelopes, acks, errors, sequence numbers, and a reconnect story clients can actually implement.

Q · Once the connection upgrades, what may each side send, what must each side answer, and how does a client that vanished for eight seconds get back to a correct state?
Server-Sent Events

One long-lived HTTP response, streaming events one way: server to client. SSE buys auto-reconnect with built-in resume (Last-Event-ID) for the price of unidirectionality — and for notifications, progress, dashboards and token streams, one way is all you needed.

Q · The server has a stream of events for the client — does the client need to talk back on the same channel, and if not, why carry a bidirectional protocol's costs?
Streaming APIs: Partial Data as a Contract

A streamed response is a sequence of commitments, not one answer. The contract must say what each chunk means, whether early chunks can be trusted before the end, how the stream announces failure mid-flight, and what a consumer resumes after a drop.

Q · When the response arrives in pieces over time, what may the consumer do with the pieces it has — and how does it learn the stream ended well, ended badly, or never really ended at all?
Long-Running Operations: 202 and the Job Resource
▶ lab

A request that takes 15 minutes cannot pretend to be request/response — some timeout between the client and your handler will fire first, and a retry starts the 15 minutes again. Return 202 with a job resource instead, and the operation becomes observable, retry-safe and cancellable.

Q · This operation takes longer than any hop in the chain will keep a connection open — so what does the client get back now, and how does it reach the result later?
The Async Job Pattern

POST the operation, get 202 and a job resource, let a worker do the work, poll or be notified, fetch the result. The pattern is simple; the contract is not — queued/running/succeeded/failed/cancelled is a state machine with retention, cancellation, progress and idempotent creation that consumers build whole workflows on.

Q · When the work outlives the request, what resource does the client hold, which states can it be in, and how does the client get the result, cancel, or retry safely?
How the Client Learns the Job Finished

Polling, webhooks, SSE/WebSocket, push notification — four ways to say "done", each with a different latency, infrastructure cost, client requirement and duplicate story. Polling with Retry-After is the documented baseline every client can use; the others are upgrades for specific consumers.

Q · For each kind of consumer, which channel delivers "your job is done" reliably enough — and what does the contract promise when that channel duplicates, reorders or misses a notification?
Slow Clients and Backpressure

A streaming or download API produces bytes faster than some consumer can take them. Where do the bytes wait, who runs out of memory first, and when does the server hang up? A contract that does not answer those questions answers them in production — usually by the whole tier falling over together.

Q · When a consumer reads more slowly than the API produces, what bounds the buffered data, what the contract promises about ordering and loss, and when the server is allowed to disconnect?