skip to content

In gRPC, what does a service stanza in a .proto file declare, and what code does each side get from it?

level: juniorimportance: must knowfreq 72%

answer

  1. the contract is the file
  2. one stanza, two generated artifacts
  3. one rpc line, three names
  4. caller gets something callable
  5. implementer gets something to fill in

basics

~20 s

A service stanza names a gRPC service and lists its rpc methods, each with one request and one response message type. The generator turns that into a callable client stub and an abstract server base type the implementation extends.

solid answer

~40 s

A `service` stanza names one gRPC service and lists its `rpc` methods; each method line names one request message type and one response message type. Feeding that file to the gRPC code generator produces two *different* artifacts from the same lines. The caller gets a **stub**: an object with one callable member per declared method, which serialises the request, performs the call and returns the response. The implementer gets an **abstract service base type**: one overridable member per method, every body left empty for you to write. The contract is therefore the file, not either artifact — both ends are derived from it, and both ends regenerating from the same revision is what keeps them compatible.

code

protobuf · 10 lines
protobuf
syntax = "proto3";

package muni.permits.v1;

// Message types are declared elsewhere in this file.
service PermitAdjudication {
  rpc SubmitApplication(SubmitApplicationRequest) returns (ApplicationReceipt);
  rpc GetApplication(GetApplicationRequest) returns (Application);
  rpc WithdrawApplication(WithdrawApplicationRequest) returns (WithdrawResult);
}

go deeper

for a junior

Point at the service and rpc lines and say what each names, then say which side receives a stub and which receives a base type to implement.

for a middle

Explain that both artifacts are derived from one file, so a caller built from generated code cannot reach a method until its own build regenerates against the current revision.

for a senior

Talk about how the generated artifacts are distributed and versioned between teams, because that, not the protocol, is where a caller and a server drift apart.

for a principal

Frame the stanza as an interface the organisation commits to: the file is published API, and its review and release process matters more than the code either side generates from it.

## What the stanza declares A `.proto` file carries two kinds of declaration that matter to a gRPC service: the **message types**, which describe data, and a **`service` stanza**, which describes callable methods. The stanza is smaller than most people expect. It gives the service a name, and each `rpc` line inside it gives one method a name, one request message type and one response message type. That is the vocabulary. Being able to say what is *absent* is half the answer. The stanza carries no host, no port, no URL path, no authentication scheme, no timeout value, no list of the errors a method may return. Anyone whose only exposure is a generated client tends to imagine the file is much bigger, and to attribute to it things the framework supplies at runtime. ## What the generator emits Running the schema through the gRPC code generator produces different artifacts for the two sides, out of the same lines: | Side | Generated artifact | What you do with it | |---|---|---| | Caller | a **stub** — one callable member per `rpc` | call it | | Implementer | a **service base type** — one overridable member per `rpc` | extend it and write the bodies | | Both | the message types the methods name | construct them and read them | The stub is finished code: it serialises the request, performs the call, and hands back the response, and you write none of that. The server artifact is deliberately unfinished — the generator knows a method's name and its two message types, and cannot possibly know what the method should *do*. ## The asymmetry is the whole idea One declaration, two shapes: something callable on one side, something to fill in on the other. Several consequences follow, and they are what an interviewer is usually probing: - **The contract is the file**, not either generated artifact. Delete both artifacts and regenerate and you are back where you started; edit an artifact by hand and the next regeneration erases you. - **Two sides can be generated from different revisions of the file** and nothing notices. There is no handshake in which the ends compare schemas; the mismatch appears only when a call names something the other side does not have. - **A method the server has not implemented is not a compile error for the caller.** The caller compiled against its own copy of the file; the gap is discovered at call time, and the server answers that call with the status `UNIMPLEMENTED (12)`. - **Adding a method to the file changes nothing for anyone who does not regenerate.** A caller built from generated code has no member for a method it has never generated. ## Generation is a build step, with the consequences of one The path from an edit to a working call has three stages, and skipping one is the everyday failure: 1. The `.proto` changes and is published somewhere both sides can get it. 2. Each side's build regenerates against that revision. 3. Each side deploys the rebuilt artifact. The new method becomes callable only once the implementer has completed all three and the caller has completed at least the first two. This is why a mature organisation treats the schema as a published dependency with a version, not as a source file that happens to live next to the server: the file's release schedule *is* every consumer's release schedule. ## What to say in an interview - Name the three parts of an `rpc` line — method name, request message type, response message type — and stop there rather than inventing a fourth. - Say **stub** for the caller and **service base type** for the implementer, and say which one is complete. Getting these backwards is the single most common stumble on this question. - Mention that the file is the contract and that both ends are derived, because every harder question about versioning, skew and rollout hangs off that sentence. - Resist explaining transport, addressing or error semantics here; none of them are declared in the stanza, and claiming they are is the error the question is looking for.

  • If the .proto is the contract, what stops a caller and a server being generated from two different revisions of it?
    Nothing in the protocol does. Each side compiles against whatever copy its build pulled in, so skew is a distribution problem, not a wire problem: publish the schema from one place, version it, and make each consumer's dependency explicit. The wire notices the skew only when a call names a method the other side does not declare.
  • Does adding an rpc line to the file change anything for callers that do not regenerate?
    No. A caller built from generated code has no member for the new method, so it cannot invoke it, and its existing calls are untouched. The generated artifact — not the file — is the surface a caller can actually reach, so a new method stays invisible until that caller rebuilds.
  • What does the service stanza deliberately not say about a method?
    Where the service is reachable, how the call is authenticated, how long the caller will wait, and how a failure is reported. The stanza declares names and message types; everything about how the call travels and how its outcome comes back is supplied by the framework at runtime.

One blueprint cut into two different tools: the caller is handed a remote control with a button per method, the implementer a panel with an empty socket behind each button.

saying these in an interview costs you the question

  • Thinks the file generates one shared class used by both sides
  • Says the server calls the stub and the caller implements the base type
  • Believes a method is callable the moment it appears in the file
  • Confuses the service stanza with the message declarations beside it
  • Assumes the schema describes the address and headers of the call