grpc: accept legacy stream protocol clients via Meta("grpc:stream:compat", "v1") - #3946
Merged
Conversation
Add opt-in backward compatibility for the pre-envelope gRPC stream protocol so services can adopt the typed stream envelope introduced in #3918 without a synchronized client/server cutover. Setting Meta("grpc:stream:compat", "v1") on a method, service or API makes generated servers accept both protocols for methods that combine Payload and StreamingPayload: generated clients now declare the envelope protocol through the goa-stream-protocol request metadata key and requests without the key are decoded using the legacy contract, payload in request metadata (goa_payload for primitives) and raw stream item frames. The .proto definition is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Adds an opt-in compatibility mode that lets generated gRPC servers accept clients speaking the pre-envelope stream protocol, alongside clients speaking the current one.
Context: #3918 changed the wire contract for gRPC methods that define both
Payload(...)andStreamingPayload(...). The one-shot payload used to be stringified into gRPC request metadata; it is now sent as a typedinitial_payloadframe inside a streamed request envelope. That change is wire-breaking: an old client cannot talk to a new server for those methods, and vice versa. Without this PR, fleets with services that call each other over such methods have to upgrade both sides of every call edge simultaneously.With this PR, a single design annotation makes the regenerated server understand both protocols, turning the migration into an ordinary rolling upgrade: servers first, clients whenever convenient, no coordinated cutover.
How to use it
Add the meta to the affected method (or to the service or API to cover all of its methods) and regenerate:
Deploy the regenerated servers. They now accept old and new clients alike.
Upgrade clients at your own pace. Clients regenerated with current Goa automatically speak the envelope protocol and identify themselves (see below); old clients keep working unchanged.
Once no legacy callers remain, delete the meta and regenerate. All compatibility code disappears from
gen/.How it works
goa-stream-protocol: 2(constants live in thegoagrpcruntime package). Legacy clients — generated by pre-envelope Goa — send no such key. The generated server checks the key once per stream (goagrpc.UsesStreamEnvelope); sniffing frame bytes would be ambiguous, the explicit key is not.initial_payloadoneof branch,Recv()unwrapsstream_itemframes.decode<Method>LegacyRequest, rebuilds the payload from request metadata exactly as pre-codegen: carry mixed gRPC streaming requests in typed envelopes #3918 servers did: one entry per payload attribute, the reservedgoa_payloadkey for non-object payloads, and aNew<Method>PayloadFromMetadataconstructor. The server stream carries alegacyflag soRecv()reads raw<Method>StreamItemframes viaRecvMsg— same field tags as the old stream message, so old frames parse as-is..protois identical with or without the meta; both message types it needs already exist. ExplicitGRPC.Metadata(...)attributes and security metadata travel in metadata under both protocols and are shared.Constraints and validation
exprvalidation). This is not a new limitation — such payloads never worked pre-codegen: carry mixed gRPC streaming requests in typed envelopes #3918 either; new designs should simply not set the meta."v1"; setting it on a method without bothPayloadandStreamingPayloadis a design error (service/API-level metas silently skip non-streaming methods, as intended).Testing
goa_payload), and mixed explicit-metadata payloads; the generated legacy decoder is shape-identical to real pre-codegen: carry mixed gRPC streaming requests in typed envelopes #3918 goldens.goagrpc.UsesStreamEnvelope.make lintclean, fullmake testgreen. The only changes to pre-existing goldens are the two envelope request encoders gaining the protocol-declaration line.Docs
dsl/meta.godocuments the new meta key.grpc/docs/FAQ.mdgains a migration recipe ("How do I upgrade services that still have clients using the pre-envelope stream protocol?").