Skip to content

feat(router): add ConnectRPC protocol support for gRPC subgraphs#2665

Closed
fengyuwusong wants to merge 4 commits intowundergraph:mainfrom
fengyuwusong:feat/connectrpc-router-integration
Closed

feat(router): add ConnectRPC protocol support for gRPC subgraphs#2665
fengyuwusong wants to merge 4 commits intowundergraph:mainfrom
fengyuwusong:feat/connectrpc-router-integration

Conversation

@fengyuwusong
Copy link
Copy Markdown

@fengyuwusong fengyuwusong commented Mar 19, 2026

Motivation and Context

The Cosmo Router currently only supports native gRPC (HTTP/2 + Protobuf) for communicating with gRPC-based subgraphs. This creates friction in environments where HTTP/2 pass-through is difficult (load balancers, CDNs) or where teams use the ConnectRPC framework.

This PR adds a grpc_protocol configuration section that allows choosing between native gRPC and ConnectRPC on a per-subgraph basis.

Related Issue

Closes #2664

Depends on: wundergraph/graphql-go-tools#1453 (RPCTransport interface + Connect transport implementation)

Changes

New config type (router/pkg/config/config.go)

  • GRPCProtocolConfig with global defaults and per-subgraph overrides for protocol (grpc/connectrpc) and encoding (proto/json)

New package (router/pkg/grpcprotocol/)

  • config.go — validation, protocol/encoding resolution with fallback chain
  • transport_builder.go — builds RPCTransport instances for Connect-configured subgraphs
  • Full test coverage (config + transport builder)

Router core integration

  • factoryresolver.go — checks Connect transports before gRPC connector (priority: Connect > gRPC > HTTP)
  • executor.go — passes connectTransports to factory resolver
  • graph_server.go — collects gRPC subgraph URLs, builds Connect transports, skips native gRPC registration for Connect subgraphs

Configuration example

grpc_protocol:
  default: grpc              # "grpc" | "connectrpc"
  default_encoding: proto    # "proto" | "json"
  subgraphs:
    my-connect-service:
      protocol: connectrpc
      encoding: json

Checklist

  • Tests or benchmarks have been added or updated.

Performance Evaluation

Connect transport adds minimal overhead compared to native gRPC — one HTTP round-trip per unary call with identical Protobuf serialization cost. JSON encoding is slightly slower but provides better debuggability. Fully backward compatible: nil config = no behavior change.

Summary by CodeRabbit

  • New Features

    • Support for gRPC protocol configuration with per-subgraph protocol and encoding selection (gRPC or Connect RPC; Proto or JSON).
    • Connect RPC added as an alternative transport so subgraphs can use HTTP-based Connect alongside native gRPC.
    • Router option to supply gRPC protocol configuration and automatic transport construction/selection.
  • Tests

    • Added unit tests covering protocol/encoding resolution, validation, and Connect transport construction.

Add a `grpc_protocol` configuration section that allows users to choose
between native gRPC and ConnectRPC on a per-subgraph basis. Connect
subgraphs communicate over HTTP/1.1 with either Protobuf or JSON
encoding, bypassing the need for HTTP/2 end-to-end.

Key changes:
- New `GRPCProtocolConfig` in config with global defaults and
  per-subgraph overrides for protocol and encoding
- New `grpcprotocol` package with config validation, resolution,
  and transport builder
- Factory resolver checks Connect transports before gRPC connector
- Connect subgraphs skip native gRPC provider registration

Depends on: wundergraph/graphql-go-tools#1453
Closes: wundergraph#2664

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 19, 2026

Walkthrough

Adds ConnectRPC transport support for gRPC subgraphs: new GRPC protocol config types and validation, Connect transport builder and tests, per‑subgraph HTTP client wiring in graph server, plumbing of connectTransports through executor/factory resolver to prefer Connect transports when configured.

Changes

Cohort / File(s) Summary
Configuration
router/pkg/config/config.go
Add GRPCProtocolConfig and SubgraphGRPCProtocolConfig; expose Config.GRPCProtocol via grpc_protocol YAML field.
Protocol utils & tests
router/pkg/grpcprotocol/config.go, router/pkg/grpcprotocol/config_test.go
Introduce protocol/encoding constants and functions Validate, ResolveProtocol, ResolveEncoding with unit tests for defaults, overrides, and validation errors.
Transport builder & tests
router/pkg/grpcprotocol/transport_builder.go, router/pkg/grpcprotocol/transport_builder_test.go
Add BuildConnectTransports to produce map[string]grpcdatasource.RPCTransport for subgraphs using ConnectRPC, selecting per‑subgraph or default HTTP clients and encoding; tests for nil/mixed/default behaviors.
Core integration: graph server & connector
router/core/graph_server.go
Wire grpcProtocol into graphServer; create per‑subgraph/default http.Clients, call grpcprotocol.BuildConnectTransports, add collectGRPCSubgraphURLs, pass connectTransports into setupConnector, and skip native gRPC connector registration for Connect subgraphs.
Core integration: resolver & executor
router/core/factoryresolver.go, router/core/executor.go
Add connectTransports map[string]grpcdatasource.RPCTransport to DefaultFactoryResolver and its constructor; resolver now returns Connect datasource factory when a transport exists; executor passes connectTransports into resolver.
Router options & config plumbing
router/core/router.go, router/core/router_config.go, router/core/supervisor_instance.go
Add WithGRPCProtocol(cfg *config.GRPCProtocolConfig) option, store core.Config.grpcProtocol, and wire the option into constructed router options.
Module file
router/go.mod
Enable a replace directive for github.com/wundergraph/graphql-go-tools/v2 pointing to a forked module version.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(router): add ConnectRPC protocol support for gRPC subgraphs' directly aligns with the main objective of implementing ConnectRPC as an alternative protocol for gRPC subgraphs.
Linked Issues check ✅ Passed The PR implementation fully addresses the coding requirements from issue #2664: adds grpc_protocol configuration with defaults and per-subgraph overrides [#2664], implements RPCTransport abstraction reusing existing gRPC pipeline [#2664], provides ConnectTransport for HTTP client reuse [#2664], maintains backward compatibility [#2664], and enables mixed gRPC/Connect deployments [#2664].
Out of Scope Changes check ✅ Passed All code changes are directly aligned with the stated objectives: configuration types, protocol/encoding resolution, transport builder, factory resolver integration, and router core wiring. The go.mod change for graphql-go-tools is necessary to support the new RPCTransport interface required by the feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@router/core/graph_server.go`:
- Around line 1661-1667: The Disabled flag in toGRPCConfiguration is incorrectly
computed from config.Plugin presence alone; update toGRPCConfiguration (in
router/core/factoryresolver.go) to consider the effective protocol (use the same
connectTransports mapping used in graph_server.go) before setting Disabled so
that subgraphs with an effective Connect transport are not marked Disabled when
plugins are off. Concretely: determine if the subgraph is using Connect (e.g.,
check if connectTransports[sg.Name] exists or otherwise derive the protocol) and
only apply the Plugin != nil && !pluginsEnabled check for native gRPC subgraphs;
leave Disabled false for connect-mode subgraphs even if config.Plugin is
non-nil. Ensure you reference the same connectTransports key lookup logic as
used around connectTransports and sg.Name in graph_server.go so behavior is
consistent.
- Around line 1248-1254: newGraphServer currently passes a nil
grpcProtocolConfig into grpcprotocol.BuildConnectTransports so ConnectRPC never
activates and the per-subgraph/default http.Clients are not wired into each
RPCTransport; update newGraphServer to use the embedded Config.GRPCProtocol (or
s.GRPCProtocol) so the protocol decision is based on Config.GRPCProtocol, and
move the call to build RPCTransport instances to the place where
per-subgraph/default http.Clients already exist (the code path that constructs
http.Clients in factoryresolver.go) so grpcprotocol.BuildConnectTransports
produces RPCTransport objects that have the actual HTTPClient baked in and can
honor router timeout/retry/circuit-breaker clients.

In `@router/pkg/grpcprotocol/config.go`:
- Around line 17-36: The Validate function in grpcprotocol (Validate) is never
called so invalid grpc_protocol/default/default_encoding/subgraph
protocol/encoding values bypass checks; either call grpcprotocol.Validate(cfg)
from the config post-unmarshal flow inside LoadConfig (ensure you avoid an
import cycle by calling via a package that already imports grpcprotocol or add a
small validation bridge), or add enum constraints for default, default_encoding
and each subgraph.protocol/subgraph.encoding in config.schema.json so the JSON
schema enforces allowed values (matching ProtocolGRPC, ProtocolConnectRPC,
EncodingProto, EncodingJSON); reference Validate, LoadConfig, ResolveProtocol
and ResolveEncoding when choosing the fix to ensure validations run before those
resolvers apply defaults.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c63d9638-5e36-4b3e-aa09-21e8bc979371

📥 Commits

Reviewing files that changed from the base of the PR and between 0e5fa81 and a80ccc2.

📒 Files selected for processing (8)
  • router/core/executor.go
  • router/core/factoryresolver.go
  • router/core/graph_server.go
  • router/pkg/config/config.go
  • router/pkg/grpcprotocol/config.go
  • router/pkg/grpcprotocol/config_test.go
  • router/pkg/grpcprotocol/transport_builder.go
  • router/pkg/grpcprotocol/transport_builder_test.go

Comment thread router/core/graph_server.go
Comment thread router/core/graph_server.go
Comment thread router/pkg/grpcprotocol/config.go
- Add grpcProtocol field to Config struct and WithGRPCProtocol option so
  the GRPCProtocol config from the YAML config is propagated into the
  graphServer (was always nil before, disabling ConnectRPC)
- Call grpcprotocol.Validate on startup to reject invalid protocol/
  encoding values early
- Pass properly-configured per-subgraph and default HTTP clients to
  BuildConnectTransports instead of nil, ensuring TLS and timeouts are
  respected for Connect subgraphs
- Wire WithGRPCProtocol(config.GRPCProtocol) in optionsFromResources
- Confirm Connect path in ResolveGraphqlFactory bypasses Disabled check,
  so the toGRPCConfiguration disabled flag is not an issue for Connect
  subgraphs (issue 2 is not a real bug)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
router/core/router_config.go (1)

147-147: Consider adding usage tracking for grpcProtocol.

Other protocol features like mcp, connectRPC, and plugins have corresponding entries in the Usage() method for telemetry. For consistency and observability, consider adding a usage entry for this new gRPC protocol configuration.

♻️ Suggested addition to Usage() method

Add something like this near the other protocol usage entries (around line 337):

usage["grpc_protocol_enabled"] = c.grpcProtocol != nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/core/router_config.go` at line 147, The Usage() method is missing
telemetry for grpcProtocol; update the Usage() implementation (the Usage()
function on the same config type that already reports mcp, connectRPC and
plugins) to add an entry such as "grpc_protocol_enabled" set to c.grpcProtocol
!= nil so the gRPC protocol config is tracked alongside the other protocol
flags; place this new usage line near the existing protocol usage entries to
keep consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@router/core/graph_server.go`:
- Around line 1253-1277: The current code builds connectSubgraphHTTPClients only
from s.subgraphTransportOptions.SubgraphMap so subgraphs that only have
per-subgraph TLS configured in s.subgraphTransports are missed; update the logic
after the existing loop to iterate s.subgraphTransports and for any subgraph key
not already in connectSubgraphHTTPClients create an *http.Client using the
transport from s.subgraphTransports (falling back to s.baseTransport if nil) and
the appropriate timeout (e.g., connectDefaultHTTPClient.Timeout or a sensible
default) so BuildConnectTransports sees per-subgraph TLS-aware clients rather
than always using connectDefaultHTTPClient.

In `@router/go.mod`:
- Line 200: Remove or comment out the local replace directive that points to the
absolute developer path for github.com/wundergraph/graphql-go-tools/v2 (the line
using "replace github.com/wundergraph/graphql-go-tools/v2 =>
/Users/.../graphql-go-tools/v2"); either comment it out to match the pattern
used in router-tests/go.mod (e.g. "// github.com/wundergraph/graphql-go-tools/v2
=> ../../graphql-go-tools/v2") or delete the replace entirely, and once
wundergraph/graphql-go-tools#1453 is merged and a release exists, update the
require directive to reference the published version instead of a local path.

---

Nitpick comments:
In `@router/core/router_config.go`:
- Line 147: The Usage() method is missing telemetry for grpcProtocol; update the
Usage() implementation (the Usage() function on the same config type that
already reports mcp, connectRPC and plugins) to add an entry such as
"grpc_protocol_enabled" set to c.grpcProtocol != nil so the gRPC protocol config
is tracked alongside the other protocol flags; place this new usage line near
the existing protocol usage entries to keep consistency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0bb94dab-9d26-4b79-b222-22997815b340

📥 Commits

Reviewing files that changed from the base of the PR and between a80ccc2 and 70e804e.

📒 Files selected for processing (5)
  • router/core/graph_server.go
  • router/core/router.go
  • router/core/router_config.go
  • router/core/supervisor_instance.go
  • router/go.mod

Comment thread router/core/graph_server.go
Comment thread router/go.mod Outdated
fengyuwusong and others added 2 commits March 19, 2026 13:50
…irective

- Include subgraphs with per-subgraph TLS (but no traffic shaping
  overrides) in Connect HTTP client map to avoid losing TLS config
- Comment out local replace directive for graphql-go-tools/v2;
  will be updated to published version once graphql-go-tools#1453 merges

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…o-tools

- Include subgraphs with per-subgraph TLS (but no traffic shaping
  overrides) in Connect HTTP client map to avoid losing TLS config
- Replace local path replace directive with fork pseudo-version
  pointing to fengyuwusong/graphql-go-tools commit 12c891d9
  (will be updated to released version once wundergraph#1453 merges)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
router/core/graph_server.go (1)

2105-2125: Consider using overridden routing URLs for Connect transports.

This function collects sg.RoutingUrl directly from the config subgraphs. However, routing URLs can be overridden via overrideRoutingURLConfiguration or overridesConfig (see configureSubgraphOverwrites starting at line 1997).

If a user overrides the routing URL for a gRPC subgraph, the Connect transport may still use the original URL instead of the override.

Consider calling collectGRPCSubgraphURLs after configureSubgraphOverwrites has processed the overrides, or passing the processed subgraphs list instead of opts.ConfigSubgraphs:

-	connectTransports := grpcprotocol.BuildConnectTransports(
-		s.grpcProtocolConfig,
-		collectGRPCSubgraphURLs(opts.EngineConfig, opts.ConfigSubgraphs),
-		connectSubgraphHTTPClients,
-		connectDefaultHTTPClient,
-	)
+	// Use processed subgraphs to respect routing URL overrides
+	grpcSubgraphURLs := map[string]string{}
+	for _, sg := range subgraphs {
+		// Check if this subgraph has gRPC config
+		for _, dsConfig := range opts.EngineConfig.DatasourceConfigurations {
+			if dsConfig.Id == sg.Id && dsConfig.GetCustomGraphql().GetGrpc() != nil {
+				grpcSubgraphURLs[sg.Name] = sg.UrlString
+				break
+			}
+		}
+	}
+	connectTransports := grpcprotocol.BuildConnectTransports(
+		s.grpcProtocolConfig,
+		grpcSubgraphURLs,
+		connectSubgraphHTTPClients,
+		connectDefaultHTTPClient,
+	)

Alternatively, refactor collectGRPCSubgraphURLs to accept the processed []Subgraph slice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/core/graph_server.go` around lines 2105 - 2125,
collectGRPCSubgraphURLs currently reads sg.RoutingUrl from the original config
and can miss user overrides applied by
configureSubgraphOverwrites/overrideRoutingURLConfiguration/overridesConfig; fix
by either calling collectGRPCSubgraphURLs only after configureSubgraphOverwrites
has been applied (so it receives the processed configSubgraphs) or refactor
collectGRPCSubgraphURLs to take the post-processed []Subgraph slice (or a
function that resolves the effective routing URL) and use that value when
building the map for gRPC subgraphs so Connect transports respect overrides.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@router/go.mod`:
- Around line 200-201: The replace directive referencing
github.com/fengyuwusong/graphql-go-tools/v2 with pseudo-version
v2.0.0-20260319034538-12c891d918df points to a non-existent commit; update the
replace to a resolvable reference by either replacing the pseudo-version with a
valid commit hash that exists in that fork, or use an available tag/branch on
github.com/fengyuwusong/graphql-go-tools/v2, or remove the replace and revert
back to the upstream module github.com/wundergraph/graphql-go-tools/v2 once PR
`#1453` is merged — edit the replace line accordingly so Go can resolve the
dependency.

---

Nitpick comments:
In `@router/core/graph_server.go`:
- Around line 2105-2125: collectGRPCSubgraphURLs currently reads sg.RoutingUrl
from the original config and can miss user overrides applied by
configureSubgraphOverwrites/overrideRoutingURLConfiguration/overridesConfig; fix
by either calling collectGRPCSubgraphURLs only after configureSubgraphOverwrites
has been applied (so it receives the processed configSubgraphs) or refactor
collectGRPCSubgraphURLs to take the post-processed []Subgraph slice (or a
function that resolves the effective routing URL) and use that value when
building the map for gRPC subgraphs so Connect transports respect overrides.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 892f5f84-93b0-457e-b6ac-d5990ed3e8c9

📥 Commits

Reviewing files that changed from the base of the PR and between 70e804e and 63ee05d.

⛔ Files ignored due to path filters (1)
  • router/go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • router/core/graph_server.go
  • router/go.mod

Comment thread router/go.mod
@alepane21
Copy link
Copy Markdown
Contributor

Hi @fengyuwusong ,
Thanks for your contribution!

This PR isn’t mergeable under our standards, as it lacks valuable unit and integration tests. The changes are also difficult to review due to limited context and supporting structure.

Using AI to assist with code is absolutely fine, but we expect contributions to meet certain quality and clarity standards. To help clarify this, our CTO recently introduced a guideline that we hope you and the broader community will find useful: https://www.human-oss.dev/

Additionally, when working on new features, we recommend opening an issue first to gather feedback before starting implementation.

I’m going to close this PR for now, but I hope this doesn’t discourage you—we really value contributions and would be happy to see a revised submission in the future.

@alepane21 alepane21 closed this Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(router): Add ConnectRPC protocol support for gRPC subgraphs

2 participants