gRPC & Contracts
gRPC (short for Google Remote Procedure Call) is an open-source framework that lets one program call a function that runs inside a different program, over a network, almost as easily as calling a function inside its own code.[1] Instead of designing URL endpoints and request bodies like you do with REST, you define a contract in a .proto file — a language-neutral description of which functions exist and what data they take. gRPC then generates matching client and server code in many languages (Go, Python, Java, C#, Node.js), so the calling code looks like a regular function call, with the network layer hidden underneath.
The one problem everything here exists to solve
Most companies do not run one program. They run many separate programs — usually called services — each with its own stored information, each able to be restarted or updated without touching the others. Sometimes one service needs to ask another to do something. Because they are genuinely separate programs, one cannot reach into the other's code or memory. The only way is to send an actual message across a network connection, and wait for a message back.
That introduces a problem that never exists inside a single program: both sides must agree, in advance and in writing, on exactly what a message contains. If one side changes what a message looks like without telling the other, both programs can keep running perfectly fine on their own while quietly misunderstanding each other for a long time before anyone notices.
That written agreement — "here is exactly what this piece of information contains, so both sides read it the same way" — is called a contract.[2] gRPC and a shared Contracts code folder are two different tools for writing and enforcing that same kind of agreement, for two different situations:
- Ask, and wait — Program A asks Program B to do something now, and waits for the answer. This is what gRPC is for.
- Announce, and move on — Program A announces "this happened" and does not wait for anyone. Zero, one, or several programs may react later, whenever they get to it. This is what shared event contracts are for.
Both situations need the same underlying agreement about message contents; they just write it in a different format, because the two tools work differently underneath.
The .proto file is the contract (ask, and wait)
gRPC solves the agreement problem by making both sides read the exact same .proto file — a plain text file, nothing more complicated than that. A small code generator reads that one file and writes ready-to-use code for both sides at once: the side asking and the side answering. Because both sides are generated from the one identical starting file, they cannot end up disagreeing about what a message contains. A change that breaks the agreement fails immediately at build time — not later, quietly, while real data is already flowing through a running system.
A complete, working example — a coffee shop with two services, a storefront and a kitchen:
syntax = "proto3";
package coffee_shop;
service KitchenGrpc {
rpc PlaceOrder (PlaceOrderRequest) returns (OrderReceipt);
rpc GetOrderStatus (GetOrderStatusRequest) returns (OrderStatus);
}
message PlaceOrderRequest {
string customer_id = 1;
repeated LineItem items = 2;
}
message LineItem {
string name = 1;
int32 quantity = 2;
int64 price_minor = 3;
}
message OrderReceipt {
string order_id = 1;
int64 total_minor = 2;
}
Line by line, in plain terms:
syntax = "proto3";— states which exact version of the file-writing rules this file follows, so any tool reading it interprets everything underneath correctly.package coffee_shop;— groups everything below under one name, so these names cannot accidentally clash with a different.protofile that reuses the same ones.service KitchenGrpc { ... }— the actual list of requests one program is allowed to make of this service. Nothing outside this list may be asked for.rpc PlaceOrder (PlaceOrderRequest) returns (OrderReceipt);— one allowed request. It names exactly what must be sent (the request message) and exactly what comes back (the response message).message— spells out exactly which pieces of information sit inside one request or reply.stringis plain text,int32a whole number, andrepeatedmeans "a list of".- The small numbers after each field (
= 1,= 2...) are just position markers, used so the message can be packed into a very small, efficient binary payload. Nothing more meaningful than that.
Once this file exists, the kitchen program generates code that waits for these requests and runs real logic when one arrives. The storefront program generates different code from that exact same file — code that lets a developer write what looks like an ordinary function call while secretly doing a real network round-trip underneath. Same file, two generated results, one on each side — and that is the entire mechanism.
The same idea without gRPC (announce, and move on)
For the "announce and move on" situation, the contract is written as a small shared piece of code instead of a .proto file — inside a folder every service builds against, usually named something like Contracts:
public record OrderPlacedIntegrationEvent(Guid OrderId, Guid CustomerId) : IntegrationEvent;
record— a named grouping of a few pieces of information that always travel together: here, anOrderIdand aCustomerId.Guid— a practically-never-repeating identifier, so no two real-world things anywhere — even ones created on completely different computers, at the same instant — can accidentally be given the same number.: IntegrationEvent— a label meaning "this is the kind of data that is allowed to be announced to other programs". The base itself is genuinely empty:
Nothing inside it at all — its only purpose is to be that label.public record IntegrationEvent;
Why does this one small file need to live in a folder shared by every program, instead of inside the program that announces it? Because every program that might react needs to read the exact same description of the announcement. If each service separately wrote its own copy of "what does an order-placed announcement look like", the copies could quietly drift apart — one side expecting a field spelled one way, another expecting something slightly different — and nothing would catch that until a real announcement arrived and was misread. One true definition, in one shared place that every program builds against, makes that kind of drift structurally impossible.
The four communication patterns
gRPC supports four types of call, unlike REST which has one (request/response):
| Pattern | What happens | Typical use |
|---|---|---|
| Unary | One request, one response — the same shape as a REST call | Most standard operations |
| Server streaming | Client sends one request; server streams back many responses | Live data feeds, log tailing, large result sets |
| Client streaming | Client streams many messages; server replies once | Uploading files in chunks, batches of sensor data |
| Bidirectional streaming | Both sides stream independently over one persistent connection | Real-time chat, collaborative tools, telemetry |
gRPC vs REST
| Feature | gRPC | REST |
|---|---|---|
| Protocol | HTTP/2 | HTTP/1.1 or HTTP/2 |
| Serialization | Protocol Buffers (binary) | JSON (text) |
| Contract | Required (.proto file) |
Optional (OpenAPI) |
| Code generation | Built-in | Optional tooling |
| Streaming | 4 patterns | Limited (SSE, WebSockets) |
| Browser support | Limited (needs gRPC-Web) | Full |
| Human-readable | No | Yes |
When to use gRPC
gRPC is the right choice for internal service-to-service communication in a Microservices Architecture: the strict contract prevents API drift between teams, the binary encoding reduces payload size and latency, and streaming covers real-time cases that REST handles awkwardly. It is widely used at Google, Netflix, and Spotify for exactly this reason.
For public APIs consumed by browsers or third-party developers, REST remains the better default — gRPC's binary format is hard to inspect, and browser support needs the gRPC-Web adapter. Many teams use both: gRPC internally between services, and REST or GraphQL externally for client-facing APIs.