skip to content

Which edits to a shared gRPC service stanza are safe to ship, and which break the teams already compiling against it?

level: seniorimportance: must knowfreq 58%

answer

  1. names, not positions, are identity
  2. adding is invisible until you regenerate
  3. renaming is removal plus addition
  4. expand, migrate, prove, contract
  5. the old method dies when traffic does

basics

~20 s

Adding an rpc method is additive — callers that never regenerate are unaffected. Renaming or removing a method, or renaming the service or its proto package, breaks every caller compiled against the old name, because those names are the call's identity.

solid answer

~40 s

Methods are identified by **name**, not by position in the file, and that single fact decides everything. Adding an `rpc` line is additive: existing callers keep working and simply cannot see the new method until they regenerate. Reordering the lines is a no-op. Renaming a method, removing one, or changing the `service` name or the `package` line is a break, because a caller compiled against the old name starts naming something the server no longer declares. The safe rollout for any of these is expand–migrate–contract: add the replacement alongside the old method, migrate consumers one at a time, prove the old method's traffic is zero, and only then delete it.

code

protobuf · 8 lines
protobuf
service PermitAdjudication {
  // Stage 1 - expand: both methods declared and implemented.
  rpc SubmitApplication(SubmitApplicationRequest) returns (ApplicationReceipt);
  rpc SubmitApplicationV2(SubmitApplicationV2Request) returns (ApplicationReceipt);

  // Safe in the same release: purely additive.
  rpc WithdrawApplication(WithdrawApplicationRequest) returns (WithdrawResult);
}

go deeper

for a junior

Remember the split: adding a method is safe, renaming or removing one is not, because the method's name is what the call carries.

for a middle

Explain why a rename equals a removal plus an addition, and what a deployed caller sees the moment the old name stops being declared.

for a senior

Run the expand–migrate–contract rollout and insist on measured per-method traffic before any removal, since a code search only finds the callers you already know about.

for a principal

Decide what the organisation guarantees about its contracts — which edits may merge freely, which need a version fork, and how long a retired method keeps answering — and encode it in the review gate.

## Names are the identity Everything about changing a service contract follows from one property: a call names its method, and matching is by name. Not by declaration order, not by an index, not by a checksum over the file. So an edit is safe exactly when it leaves every name an existing caller can produce still meaning the same thing on the server. That also means the two sides never negotiate. Nobody compares schemas at connect time. A contract mismatch is discovered one call at a time, by the call failing. ## The additive edits These cost a deployed consumer nothing, even one that never rebuilds: - **Adding an `rpc` line.** Existing callers have no member for it and carry on unchanged. - **Adding a whole new `service` stanza**, for the same reason. - **Reordering the `rpc` lines**, or reformatting the file. Position carries no meaning. - **Editing comments.** Worth saying out loud, because candidates sometimes hesitate. The cost of an addition is not correctness, it is reach: nobody can call the new method until their own build regenerates and redeploys, so the addition is invisible for as long as that takes. ## The breaking edits | Edit | What a deployed caller experiences | |---|---| | Rename an `rpc` method | that one method is answered `UNIMPLEMENTED (12)`; others unaffected | | Remove an `rpc` method | same, permanently | | Rename the `service` | every method under it fails at once | | Change the `package` line | every method under it fails at once | | Swap a method's request or response message type | the call is decoded against a different definition, so fields that do not line up are dropped or defaulted instead of failing loudly | The last row is the dangerous one, because it does not announce itself. The others fail visibly; that one can return a plausible-looking success built from half-empty data. Treat a type swap as a new method with a new name. Think of a rename as **a removal and an addition in one edit**, and it stops looking harmless: nothing about the messages changed, and that is exactly why people ship it by accident during a tidy-up. ## Expand, migrate, contract The rollout that works across teams deploying independently has four stages, and its cost is time, not effort: 1. **Expand.** Add the replacement method beside the one it replaces. Both are declared, both are implemented, both answer. The contract is temporarily redundant, on purpose. 2. **Migrate.** Each consuming team switches when it next releases. No coordinated window, no shared date. 3. **Prove.** Measure per-method call counts at the server over a window long enough to catch monthly and quarterly jobs. Zero for the whole window, from *every* caller, not just the ones you know about. 4. **Contract.** Remove the old method, publish the revision, and let consumers pick it up whenever they rebuild. Stage 3 is the one teams skip, and it is the only one that produces evidence. Searching other repositories for call sites finds the callers you can see; a per-method counter finds the ones you cannot. ## Two rules worth stating explicitly - **Do not reuse a retired method name for different behaviour.** A stale caller would reach the new implementation with old expectations and be answered *successfully* — a silent wrong result in place of a loud failure. Names are cheap; pick a new one. - **A deliberate break gets a new package, not an edit.** When the contract genuinely has to change shape, fork the proto package to a new version segment and serve both, so consumers migrate on their own schedule instead of all on one evening. (Changes *inside* the request and response message types follow their own compatibility rules and are a separate subject from the service stanza discussed here.) ## Mistakes to avoid - Calling a rename safe because the message types were untouched. - Removing a method because a search found no call sites. - Believing consumers pick up a new method without rebuilding. - Worrying about the order of `rpc` lines, which carries no meaning at all. - Planning a big-bang cutover in which every team deploys within the same window.

  • Is changing an rpc method's request message type to a different message a safe edit?
    No, and it is the worst kind of unsafe because it can fail quietly. The server decodes the caller's bytes against a different definition, so fields that do not line up are dropped or defaulted rather than rejected, and the call may succeed with partly empty data. Treat it as a new method under a new name.
  • How do you know it is safe to delete a method that looks unused?
    By measuring, not by searching. Count calls per method at the server over a window long enough to cover monthly and quarterly jobs, and require zero across the whole window from every caller. Announce the removal, keep the method answering for one full release after the count reaches zero, then delete it.
  • Once a method is removed, may its name be reused later for something else?
    Avoid it. A caller still holding an old generated artifact would reach the new implementation with old expectations and be answered successfully — a silent wrong result instead of the loud failure a retired name would have given. Pick a new name; there is no shortage of them.

saying these in an interview costs you the question

  • Calls a method rename safe because the messages are unchanged
  • Deletes a method after a code search finds no callers
  • Thinks callers pick up a new method without rebuilding
  • Treats reordering rpc lines in the file as a breaking change
  • Plans a big-bang cutover with every team deploying at once
  • Reuses a retired method name for different behaviour