The API decision framework
The criteria axes for choosing a pattern, style, and transport — the lens this book applies in a Decision callout at the end of every chapter.
“Just use REST” and “just use gRPC” are both wrong for the same reason: neither names the property of the interaction that makes it true. REST is the right call for a browser fetching a booking by id because the interaction is a cacheable, resource-shaped request/response over a transport every corporate proxy already understands — not because REST is the default. gRPC is the right call for a routing engine calling a capacity-check service a thousand times a second because the interaction is latency-sensitive, strongly typed, and entirely server-to-server — not because gRPC is newer. Swap the two interactions and both answers invert. A framework that only ever produces one answer isn’t a framework; it’s a habit wearing a framework’s clothes.
This chapter builds the lens every later chapter uses instead of a habit: a fixed set of axes you read off an interaction before picking its pattern, style, and transport. Every chapter from here forward closes with a short Decision callout that places its topic on these same axes, so the vocabulary introduced here is worth learning once and reusing twenty-five more times.
Why cargo-culting a single style fails
A shipping platform is not one interaction repeated at different URLs. Creating a booking, polling whether that booking’s routing finished, streaming a live feed of a vessel’s cargo events, and pushing a webhook to a customs broker when a container clears inspection are four interactions with four different shapes, and treating them identically produces four wrong designs. Force the routing confirmation through a synchronous REST call and the client either blocks for the thirty seconds routing takes or the API returns a premature 200. Force the live cargo-event feed through request/response polling and the client either misses events between polls or hammers the server into a de facto denial-of-service. Force the customs webhook through a client-initiated poll and a time-sensitive compliance event waits for the client to ask, which defeats the point of a push.
The fix is not a better single style; it’s naming the property of each interaction that decides the style for it. That’s what an axis is: a question about the interaction, not about the technology. Asked in order, the seven axes below cover the trade-offs that recur across this entire book.
The axes
Interaction shape. Is this a request that expects exactly one response (request/response), a sequence of values produced over time (streaming), or a signal nobody needs to acknowledge in-line (fire-and-forget event)? This is the first axis to check because it eliminates whole categories before the rest matter. A request/response shape fits REST, gRPC unary calls, or GraphQL queries. A streaming shape pushes toward Server-Sent Events, chunked transfer encoding, WebSockets, or a gRPC streaming RPC — covered in Chapter 16 and Chapter 17. A fire-and-forget shape, where the sender doesn’t wait and the receiver reacts whenever it reacts, pushes toward an event broker or a webhook, not a REST call at all, however tempting it is to model “notify the customs broker” as a POST.
Payload size & shape. A booking record is a few hundred bytes of nested JSON; a cargo manifest for a container ship is tens of megabytes of binary. REST handles the first case natively and the second case only with help — Range requests, HEAD for metadata, chunked delivery — which is the entire subject of Chapter 15. Deeply nested, schema-flexible payloads with client-chosen shape tilt toward GraphQL, which exists specifically to let a client request a projection of a graph rather than accept whatever shape a REST resource hands back. Small, fixed-schema, high-frequency payloads tilt toward gRPC’s binary Protocol Buffers encoding, where JSON’s self-describing text format is pure overhead paid on every call.
Latency & real-time need. A booking lookup that returns in 80ms over REST is fine for a dashboard. A ship-to-shore radar correlation that needs a 5ms round trip is not a REST problem at all, because HTTP/1.1 request/response, connection setup, and JSON parsing all cost time a hard real-time budget can’t spend. There’s no universal number; the threshold is whether the interaction’s correctness depends on the response arriving within a bound the caller can name. When it does, look at gRPC over HTTP/2, binary framing, and — per Chapter 19 — whether HTTP/3’s connection migration and independent stream multiplexing remove a bottleneck HTTP/2 can’t.
Cacheability. This axis is where REST’s resource model pays for itself. A GET /bookings/{id} response is a named, addressable resource that an intermediate cache, a CDN, or the browser’s own HTTP cache can store and revalidate using ETag and Cache-Control without the application ever being asked again. A GraphQL query over POST or a gRPC unary call carries no such built-in handle — caching either requires bespoke application-level work. If an interaction is read-heavy, idempotent, and the same request shape recurs across many clients, cacheability alone is often reason enough to pick REST over an alternative that would otherwise look equally plausible on every other axis.
Client diversity. Who calls this endpoint matters as much as what it does. A public booking-lookup endpoint called from browsers, mobile apps, and third-party logistics partners needs a contract that’s documented, versioned, and consumable without a code generator tied to one language — REST’s text-based JSON over HTTP, with an OpenAPI contract, is the lowest common denominator every one of those clients already speaks. A capacity-check RPC called only by routing services the same team owns can afford gRPC’s generated stubs and shared .proto files, because there’s no third party to hand a .proto file to and no browser in the picture that can’t easily speak HTTP/2 trailers. The more heterogeneous and external the client population, the harder the gravity toward REST.
Evolution & versioning needs. Some interactions change shape constantly as the business adds cargo types, routing rules, and compliance fields; others are stable contracts that outside parties depend on for years. REST’s independent resources and content negotiation (Chapter 14 covers URI, query, header, and media-type versioning) let one resource evolve without forcing every client to upgrade in lockstep. A tightly coupled internal RPC between two services deployed together can tolerate a stricter, less flexible contract because both sides change in the same release. The more external consumers an interaction has, and the less control you have over when they upgrade, the more an interaction needs REST’s loose coupling rather than a strict typed contract shared at compile time.
Operational constraints. The network between a client and a service is rarely neutral. A browser cannot open an arbitrary gRPC connection without a proxy translating it, because browsers don’t expose the HTTP/2 trailer frames gRPC depends on. A corporate firewall that only permits outbound 80/443 and inspects payloads will happily pass REST’s plain HTTP and balk at a raw TCP WebSocket upgrade or an unfamiliar binary protocol. A CDN in front of a public API caches REST responses by URL without configuration and does nothing useful in front of a gRPC or GraphQL endpoint without purpose-built support. When the path between caller and service includes infrastructure you don’t control, that infrastructure votes, and it usually votes for REST over plain HTTP.
Anti-pattern
Picking a style because it's the one the team already knows, then forcing every interaction through it regardless of shape, is the same mistake as cargo-culting the newest one. Familiarity is not an axis. If none of the seven axes above favor REST for a given interaction, the right move is to say so and reach for the style that fits — this book's sibling titles on gRPC, GraphQL, and WebSockets exist for exactly that branch.
A worked decision: confirming a booking
Apply the axes to a concrete question: when a customer submits a booking, should confirming it — running the routing engine and the capacity check that together decide whether the cargo can move — be a synchronous REST call, an asynchronous REST operation, or an event?
Start with interaction shape. Confirmation isn’t instantaneous: routing a multi-leg voyage against live capacity can take anywhere from a second to half a minute, well past what a client should sit blocked on a single HTTP connection waiting for. That alone rules out a plain synchronous POST /bookings/{id}/confirmation that returns 200 with the confirmed booking — the shape of the work doesn’t match the shape of a single request/response round trip.
Payload size and cacheability add little here; the request and response bodies are small JSON documents, and a confirmation is a one-time state transition, not a value worth caching. Latency sensitivity matters, but asymmetrically: the client submitting the confirmation can tolerate seconds to minutes of total wait, as long as it isn’t blocked synchronously the whole time, which points toward decoupling the acceptance of the request from the delivery of the result rather than toward a faster transport.
Client diversity turns out to be the decisive axis. The caller requesting confirmation is anything that can book cargo — a browser, a mobile app, or a partner’s server — and all of them already hold a REST session against this API. Evolution and versioning favor the same answer: confirmation is part of the same booking lifecycle as every other state transition in Chapter 12’s finite-state machine, and keeping it on the same contract avoids standing up a second protocol just for one operation. Operational constraints confirm it: whatever proxy or firewall already allows these clients to call POST /bookings allows them to call this endpoint too.
Those axes land on REST, but not the synchronous kind — the asynchronous operation pattern Chapter 10 covers in full: POST /bookings/{id}/confirmation returns 202 Accepted with a Location header pointing at an Operation resource, the client polls that resource, and a 303 See Other on completion redirects to the confirmed booking. Every axis that ruled out a blocking call is satisfied without leaving REST at all.
That’s the client-facing half. A second question hides behind the first: once the booking is confirmed, how does the warehouse system, the customs broker, and the carrier’s own tracking feed find out? None of those three is the client who asked for the confirmation, none of them is polling anything, and there may be an unknown number of them subscribed at any given time. Run the interaction-shape axis again and the answer changes: this is a fire-and-forget event, not a request/response, because the confirming service has no reason to know or care who’s listening. Client diversity points the same direction — an unbounded set of internal and downstream consumers is exactly the shape an event stream serves and a point-to-point REST call doesn’t. The same state transition that produced a 202/poll/303 sequence for the one caller who asked also emits a BookingConfirmed event onto a stream that anything can subscribe to, independent of the request that triggered it — the subject of Chapter 21’s multi-protocol contracts.
One state transition, two correct answers, because two different interactions were hiding inside what looked like a single “confirm the booking” requirement. That’s the habit this framework is meant to build: before reaching for a pattern, name the interaction’s shape, payload, latency need, cacheability, audience, lifecycle, and network path, and let those seven answers point at the style rather than picking the style first and rationalizing it afterward.
The Decision callout, going forward
Every remaining chapter in this book ends with a callout labeled Decision, and it is always the same move: take the chapter’s topic, run it against these seven axes, and state in two or three sentences where it lands and why. It is not a summary of the chapter — it’s the chapter’s conclusion translated into the shared vocabulary established here, so that skimming every Decision callout in sequence reconstructs the book’s argument without re-reading the prose that built it. When a later chapter’s Decision callout says an interaction is “synchronous, resource-shaped, cache-friendly, and serves diverse clients,” it is invoking interaction shape, cacheability, and client diversity by name, trusting that this chapter already did the work of defining what those words mean and why they matter. Read this chapter once, and the one-paragraph Decision box at the bottom of Chapter 5, Chapter 10, Chapter 17, and every chapter after them becomes a complete, checkable argument instead of a slogan.
Decision
Apply the axes: when the interaction is synchronous in the caller's mind, resource-shaped, cache-friendly, and serves a diverse or external client population, REST wins outright. When the work behind that interaction can't finish inside one request/response cycle, REST still wins — as the asynchronous operation pattern (202 → poll → 303) rather than a blocking call. When the real requirement is notifying an unknown or unbounded set of downstream consumers rather than answering the one caller who triggered the change, the interaction shape axis says to leave request/response altogether for an event stream, which composes with the REST contract rather than replacing it.