# gRPC — Part 2: Streaming Modes and When to Actually Choose It

Part 1 covered `.proto` contracts, codegen, the binary wire format over HTTP/2, and unary calls. This part covers the three streaming call shapes HTTP/2's multiplexed, long-lived connections make possible, and closes with a direct tradeoff comparison against REST and GraphQL.

## 1. Four Call Shapes, Declared in the `.proto` Itself

The `stream` keyword on either side of an `rpc` declaration marks which direction(s) are streamed:

```protobuf
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);                          // unary (Part 1)
  rpc ImportOrders(stream OrderInput) returns (ImportSummary);            // client streaming
  rpc WatchOrderStatus(WatchRequest) returns (stream OrderStatusUpdate);  // server streaming
  rpc SyncOrders(stream OrderInput) returns (stream SyncResult);          // bidirectional streaming
}
```

## 2. Client Streaming: Many Requests, One Response

The client sends a sequence of messages over one call, then the server responds once after the client signals it's done sending:

```ts
const call = client.importOrders();
for (const order of largeOrderBatch) {
  call.write(order); // send one at a time, doesn't wait for a response per item
}
const summary = await call.end(); // signal "done sending", await the single final response
```

Useful for uploading a large dataset (bulk import) without buffering the entire payload into one giant request message first — each item streams as it's ready.

## 3. Server Streaming: One Request, Many Responses

The inverse — one request, then the server pushes a sequence of messages over time:

```ts
const stream = client.watchOrderStatus({ orderId: "482" });
stream.on("data", (update: OrderStatusUpdate) => {
  console.log("status changed to", update.status); // fires every time the server has a new update
});
```

Conceptually similar to GraphQL's `Subscription` (`12-graphql` Part 2, section 4) or a Server-Sent Events endpoint — the server keeps pushing until the call ends, without the client polling.

## 4. Bidirectional Streaming: Both Sides, Independently, Concurrently

Both client and server send messages on the same call, and — critically — **not in lockstep**: each side reads and writes independently, at its own pace, over the same long-lived HTTP/2 stream:

```ts
const call = client.syncOrders();
call.write(localChange1); // client can keep writing...
call.on("data", (result: SyncResult) => { /* ...while also receiving server pushes, interleaved */ });
call.write(localChange2);
call.end(); // signal done sending; the server may still be sending its own remaining responses
```

This is the shape with no clean REST or plain-HTTP equivalent at all — REST's request/response model has no notion of two independent, concurrent, long-lived message streams over one call. It requires HTTP/2's multiplexing (Part 1, section 3) as a hard prerequisite.

## 5. gRPC vs. REST vs. GraphQL — Choosing Deliberately

| | REST | GraphQL | gRPC |
|---|---|---|---|
| **Contract** | OpenAPI (documentation, can drift — `11-api_documentation`) | Schema (enforced live) | `.proto` (compile-time, codegen both sides) |
| **Format** | JSON, human-readable | JSON, human-readable | Binary, not human-readable |
| **Best fit** | Public APIs, broad client compatibility, browser-friendly | Client-driven UIs with varied/nested data needs | Internal service-to-service calls, performance-sensitive paths |
| **Streaming** | Not native (polling, SSE, or a separate WebSocket) | Subscriptions (own transport) | Native, 4 call shapes, one framework |
| **Browser support** | Native (`fetch`) | Native (`fetch`, plain JSON over HTTP) | Requires `grpc-web` proxy — browsers can't speak raw gRPC/HTTP2-trailers directly |
| **Debuggability** | `curl`, browser devtools, trivial | `curl`, GraphiQL/Playground, trivial | Needs protobuf-aware tooling (`grpcurl`) |

The practical rule of thumb this table implies: gRPC's performance and strict-contract wins matter most for **internal, service-to-service** traffic (microservice-to-microservice calls inside `distributed-systems`'s architecture, where both ends are services you control and can regenerate stubs for on every deploy) — its binary format and lack of native browser support make it a poor fit as the *public*, browser-facing API surface, where REST or GraphQL's human-readable, `fetch`-native properties matter more than raw throughput.

## Common Pitfalls

1. **Choosing gRPC for a public, browser-facing API "because it's faster."** Section 5's browser-support row is the practical blocker — you'd need a `grpc-web` proxy layer just to reach it from a browser, adding complexity the performance win rarely justifies for public traffic.
2. **Assuming bidirectional streaming means request-response pairing.** Section 4's "not in lockstep" point is easy to miss — messages on each side are independent, not a 1:1 request/reply sequence like a chat turn-by-turn model might suggest.
3. **Forgetting client-streaming calls need an explicit "done sending" signal.** The server-side handler for `ImportOrders` doesn't know the client is finished until `.end()`/equivalent is called — omitting it leaves the call hanging indefinitely.
4. **Using gRPC where a single unary call already breaks a large payload into hundreds of RPCs instead of one client-streaming call.** If you find yourself calling a unary RPC in a tight loop for bulk data, that's exactly the shape client streaming (section 2) exists to replace.

## Quick Reference

```
Unary               -- 1 request, 1 response (Part 1)
Client streaming     -- many requests, 1 response (bulk upload)
Server streaming      -- 1 request, many responses (live updates, like a subscription)
Bidi streaming          -- both sides stream independently, NOT in lockstep -- needs HTTP/2

Choose gRPC for:  internal service-to-service, performance-sensitive, streaming needs
Choose REST/GraphQL for: public/browser-facing APIs -- native fetch support, human-readable, debuggable
```
