Skip to content

WolverineFx.Http.Grpc — gRPC Support for Wolverine HTTP Endpoints#2281

Closed
erikshafer wants to merge 49 commits intoJasperFx:mainfrom
erikshafer:copilot/add-grpc-support-to-wolverine-again
Closed

WolverineFx.Http.Grpc — gRPC Support for Wolverine HTTP Endpoints#2281
erikshafer wants to merge 49 commits intoJasperFx:mainfrom
erikshafer:copilot/add-grpc-support-to-wolverine-again

Conversation

@erikshafer
Copy link
Copy Markdown
Contributor

@erikshafer erikshafer commented Mar 10, 2026

Add gRPC Support and Streaming Handler Capabilities to Wolverine

This PR adds comprehensive gRPC support to Wolverine, enabling both code-first (protobuf-net.Grpc) and proto-first (.proto files + Grpc.Tools) approaches for building gRPC services with Wolverine's handler pipeline. Additionally, it implements streaming handler support with OpenTelemetry instrumentation for the core Wolverine message bus.

What's Included

1. gRPC Code Generation (Added March 12, 2026)

  • Automatic Service Wrapper Generation: Wolverine now generates gRPC service wrapper classes that delegate to your handlers through the message bus
  • GrpcChain: New code generation chain that implements ICodeFile for generating service wrappers
  • Selective Code Generation: Only generates wrappers for handlers that need them (streaming, multi-handler scenarios)
  • Debug Support: Generated code appears in Visual Studio debugger with proper line mappings

How it works:

// Your handler (conventional Wolverine handler)
public class PingHandler
{
    public Pong Handle(Ping ping) => new Pong { Message = $"Pong: {ping.Message}" };
}

// Wolverine generates this service wrapper automatically:
public class PingService : global::Ping.PingBase
{
    private readonly IMessageBus _bus;

    public PingService(IMessageBus bus) => _bus = bus;

    public override async Task<Pong> Handle(Ping request, ServerCallContext context)
        => await _bus.InvokeAsync<Pong>(request, context.CancellationToken);
}

2. Streaming Handler Support ✨ NEW

Implemented streaming handler support with comprehensive telemetry integration:

Message Bus Streaming API (Commits 296b09b, 91e6554, 7aa7cb0, 7629b3f) ✅ Complete

  • IMessageBus.StreamAsync<TResponse>(): New public API for streaming responses from handlers
  • Handler Integration: Handlers can return IAsyncEnumerable<T> and Wolverine's pipeline will stream results
  • Pipeline Support: Streaming handlers work through Wolverine's full middleware pipeline (cascading, side effects, etc.)

Usage Example:

// Handler
public class CountHandler
{
    public async IAsyncEnumerable<CountResponse> Handle(CountRequest request)
    {
        for (int i = 1; i <= request.Count; i++)
        {
            await Task.Delay(10);
            yield return new CountResponse(i);
        }
    }
}

// Consumer
await foreach (var response in messageBus.StreamAsync<CountResponse>(new CountRequest(10)))
{
    Console.WriteLine($"Received: {response.Value}");
}

Implementation Details:

  • IMessageInvoker: Extended with StreamAsync<TResponse>() method
  • Executor: Implements streaming execution path with telemetry
  • MessageBus: Public streaming API
  • Remote Routing: MessageRoute and TopicRouting correctly throw NotSupportedException for streaming over message transports (use gRPC for remote streaming)

gRPC Streaming Integration (Commit 7629b3f, 2b2fce4) ✅ Complete

Wolverine now supports a recommended architectural pattern for gRPC streaming endpoints:

  • Thin gRPC endpoint - Protocol adapter that handles gRPC-specific concerns
  • Rich Wolverine handler - Business logic that returns IAsyncEnumerable<T>
  • Message bus integration - Connect them via Bus.StreamAsync<T>()

Example:

// 1. Wolverine streaming handler (business logic)
public class RaceStreamHandler
{
    public async IAsyncEnumerable<RacePosition> Handle(RacerUpdate update)
    {
        // Process update and yield results
        yield return new RacePosition { /* ... */ };
    }
}

// 2. gRPC endpoint (thin protocol adapter)
public class RacingGrpcService : WolverineGrpcEndpointBase, IRacingService
{
    public async IAsyncEnumerable<RacePosition> RaceAsync(
        IAsyncEnumerable<RacerUpdate> updates,
        CallContext context = default)
    {
        await foreach (var update in updates.WithCancellation(context.CancellationToken))
        {
            // Delegate to Wolverine handler via message bus
            await foreach (var position in Bus.StreamAsync<RacePosition>(update, context.CancellationToken))
            {
                yield return position;
            }
        }
    }
}

Benefits:

  • ✅ Separation of concerns (protocol vs. business logic)
  • ✅ Automatic OpenTelemetry instrumentation
  • ✅ Full middleware pipeline execution
  • ✅ Handler testability without gRPC infrastructure
  • ✅ Handler reusability across protocols

Enhanced OpenTelemetry for Streaming (Commits 296b09b, 13fa8d1) ✅ Complete

  • StreamingTelemetryExtensions.WithTelemetry(): Extension method for instrumenting IAsyncEnumerable<T> streams
  • Comprehensive Tracing: Streaming operations create proper OpenTelemetry spans with events for each item
  • Automatic Integration: Streaming handlers get telemetry automatically when executed through the pipeline
  • Manual Instrumentation: Can also manually instrument streams with .WithTelemetry(activity, "operation-name")

Telemetry Features:

  • Creates child span for streaming operation
  • Records events for each streamed item (with position tracking)
  • Tracks total item count and completion status
  • Handles errors and cancellation gracefully
  • Adds relevant tags (messaging.operation, stream.position, stream.total_items)

Test Coverage for Streaming 📊

Added 15+ comprehensive tests across 3 test files:

  1. CoreTests/Streaming/StreamingHandlerTests.cs (3 tests)

    • Basic streaming through message bus
    • Empty stream handling
    • Cancellation token support
  2. CoreTests/Streaming/streaming_invoker_behavior_tests.cs (7 tests)

    • IAsyncEnumerable handler support
    • Exception handling after yielding items
    • Cancellation token propagation
    • Null message validation
    • Multiple concurrent consumers
    • Edge cases and error scenarios
  3. Wolverine.Http.Grpc.Tests/streaming_telemetry_tests.cs (6 tests)

    • OpenTelemetry span creation
    • Event recording for each item
    • Total item count tracking
    • Error handling telemetry
    • Activity context propagation
    • Tag and attribute validation

Testing Philosophy: Wolverine tests streaming through integration tests using the public API (IMessageBus.StreamAsync), which matches the framework's existing testing philosophy for Executor, MessageRoute, and TopicRouting classes.

Not Implemented Yet ⏸️

  • Advanced Streaming Control: Backpressure, batching, throttling mechanisms (deferred for future work)
  • Streaming Error Policies: Retry, circuit breaker for streams (deferred for future work)

3. Proto-First gRPC Support

  • .proto File Integration: Wolverine works seamlessly with .proto files and Grpc.Tools
  • Standard gRPC Tooling: Use <Protobuf Include="*.proto" /> in your .csproj - no custom build steps
  • Handler Discovery: Wolverine discovers handlers for your proto-generated message types automatically
  • Documentation: Comprehensive FAQ section explaining when to use proto-first vs code-first

4. Code-First gRPC Support (protobuf-net.Grpc)

  • Attribute-Based Services: Use [ServiceContract] and [OperationContract] attributes
  • C# Types as Contracts: Define services using interfaces and C# types
  • Automatic Service Registration: Wolverine discovers and registers gRPC services automatically

5. Enhanced gRPC Integration

  • HTTP/2 Configuration: Proper Kestrel HTTP/2 setup for gRPC
  • Service Discovery: Automatic discovery and registration of gRPC services in Wolverine's DI container
  • Abstract Service Support: Support for ServiceBase<TService> abstract classes from gRPC tooling
  • Testing Improvements: Comprehensive test suite with 50+ tests in Wolverine.Http.Grpc.Tests

Key Files Changed

Streaming Implementation

  • src/Wolverine/IMessageBus.cs - Added StreamAsync<TResponse>() public API
  • src/Wolverine/Runtime/MessageBus.cs - Implemented streaming through message bus
  • src/Wolverine/Runtime/Routing/IMessageInvoker.cs - Extended invoker interface for streaming
  • src/Wolverine/Runtime/Handlers/Executor.cs - Added streaming execution path with telemetry
  • src/Wolverine/Runtime/Handlers/NoHandlerExecutor.cs - Streaming support for no-handler case
  • src/Wolverine/Runtime/Routing/MessageRoute.cs - Streaming NotSupported for remote invocation
  • src/Wolverine/Runtime/Routing/TopicRouting.cs - Streaming NotSupported for topic routing
  • src/Http/Wolverine.Http.Grpc/StreamingTelemetryExtensions.cs - OpenTelemetry instrumentation
  • src/Testing/CoreTests/Streaming/* - Comprehensive streaming tests (15+ tests)

Code Generation & gRPC Core

  • src/Http/Wolverine.Http.Grpc/GrpcChain.cs - Code generation for service wrappers
  • src/Http/Wolverine.Http.Grpc/WolverineGrpcExtensions.cs - Service discovery and registration
  • src/Samples/ProtoFirstGrpcSample/ - Proto-first example
  • src/Samples/PingPongWithGrpc/ - Code-first unary example
  • src/Samples/RacerGrpcSample/ - Bidirectional streaming + IMessageBus.StreamAsync integration example

Sample Projects & Documentation

All three gRPC samples demonstrate different patterns:

  1. PingPongWithGrpc - Simple unary (request/reply) using code-first gRPC
  2. ProtoFirstGrpcSample - Simple unary using proto-first gRPC
  3. RacerGrpcSample - Bidirectional streaming + Wolverine streaming handler integration (commits 7629b3f)
    • Demonstrates IMessageBus.StreamAsync<T>() integration
    • Shows separation of concerns: thin gRPC endpoint → rich Wolverine handler
    • Handler returns IAsyncEnumerable<T> and executes through middleware pipeline
    • Automatic OpenTelemetry instrumentation for streaming operations

Documentation Updates (Commit 2b2fce4):

  • Updated docs/guide/http/grpc.md with comprehensive streaming handler documentation
  • Added section "Streaming with IMessageBus.StreamAsync (Recommended Pattern)"
  • Included benefits, examples, and usage patterns for streaming handlers
  • Updated RacerGrpcSample reference to highlight Bus.StreamAsync() integration

For Wolverine Maintainers (JasperFx Team)

This PR contains two main components:

  1. gRPC Support (Add-on Package) - New WolverineFx.Http.Grpc package that does NOT change existing Wolverine behavior
  2. Streaming Handler Support (Core Feature) - Extends core Wolverine with IMessageBus.StreamAsync<T>() benefiting all users

Key design decisions:

  • Supports two gRPC approaches under one package: code-first (protobuf-net.Grpc) and proto-first (.proto + Grpc.Tools), because both have strong real-world use cases
  • Convention-based discovery (GrpcEndpoint, GrpcEndpoints, GrpcService, GrpcServices suffixes) mirrors the established Wolverine handler naming convention
  • [WolverineGrpcService] attribute opt-in for proto-first services that must inherit proto-generated base classes
  • WolverineGrpcEndpointBase base class gives code-first services zero-boilerplate IMessageBus access via property injection
  • Streaming support extends Wolverine's core capabilities beyond gRPC - any handler can now return IAsyncEnumerable<T> and be consumed via IMessageBus.StreamAsync<T>()
  • Architectural pattern promotes separation of concerns: thin protocol adapters (gRPC endpoints) delegate to rich Wolverine handlers via message bus

Breaking Changes

None. This is purely additive functionality.

Testing

  • ✅ 50+ tests in Wolverine.Http.Grpc.Tests
  • ✅ 15+ streaming tests in CoreTests/Streaming
  • ✅ Integration tests with gRPC clients and services
  • ✅ OpenTelemetry telemetry validation tests
  • ✅ Edge case and error scenario coverage
  • ✅ All sample projects build and run successfully

Migration Guide

No migration needed - existing Wolverine applications are unaffected. To adopt gRPC:

For Code-First:

builder.Services.AddGrpc();
builder.Host.UseWolverine(opts => {
    // Your handlers work as-is
});

app.MapWolverineGrpcServices(); // Auto-discovers and maps services

For Proto-First:

// 1. Add .proto file with Grpc.Tools reference
// 2. Write conventional Wolverine handlers for your proto messages
// 3. Wolverine generates the service wrapper automatically

For Streaming:

// Handler returns IAsyncEnumerable<T>
public async IAsyncEnumerable<Result> Handle(Query query)
{
    await foreach (var item in FetchItems())
        yield return item;
}

// Consumer uses StreamAsync
await foreach (var result in bus.StreamAsync<Result>(new Query()))
{
    // Process streamed results
}

// gRPC endpoint delegates to handler (recommended pattern)
public async IAsyncEnumerable<Result> StreamResults(Query query, CallContext ctx)
{
    await foreach (var result in Bus.StreamAsync<Result>(query, ctx.CancellationToken))
    {
        yield return result;
    }
}

Notes for Reviewers

  1. Code Generation: The GrpcChain class generates service wrappers at build time - check obj/ folder for generated code during debugging
  2. Streaming Design: Streaming handlers must return IAsyncEnumerable<T> - single return values are not automatically converted to streams (by design)
  3. Remote Streaming: Streaming over message transports (RabbitMQ, Azure Service Bus, etc.) correctly throws NotSupportedException - use gRPC for remote streaming scenarios
  4. Telemetry: OpenTelemetry integration is automatic for handlers invoked through IMessageBus.StreamAsync()
  5. Test Philosophy: Streaming is tested through integration tests using public API, matching Wolverine's existing approach for executor/routing tests
  6. Core Framework Changes: While gRPC is an add-on package, streaming support (IMessageBus.StreamAsync) is added to core Wolverine and benefits all users
  7. Sample Integration: RacerGrpcSample demonstrates the recommended architectural pattern for streaming: thin gRPC endpoints that delegate to Wolverine streaming handlers via Bus.StreamAsync()
  8. Documentation: Comprehensive documentation updates explain the streaming handler pattern and its benefits

Copilot AI and others added 30 commits March 9, 2026 22:47
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…ing in docs

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
… design decisions

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…, updated tests

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…ct proto-first support

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…test

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…ing docs

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
… entries

- Replace all 'ASP.NET Core' with 'ASP.Net Core' to match Wolverine convention
- Rename 'Option A/B/C' service class alternatives to descriptive names
  (convention-based, attribute-based, attribute + constructor injection)
- Rename 'Add a gRPC endpoint adapter' step to 'Write the gRPC service class'
- Fix empty first-column header in code-first vs proto-first comparison table
- Replace bare path strings with GitHub hyperlinks for all three sample projects
- Rename 'Roadmap' section to 'Design Notes' (more appropriate for a docs page)
- Tighten intro prose in Code-First Approach section (one sentence, not two)
- Add PingPongWithGrpc, ProtoFirstGrpcSample, RacerGrpcSample to samples.md"

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…ces)

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
… (code-first pattern, no constructor needed)

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
… contributors

- Reduced verbosity in XML documentation throughout Wolverine.Http.Grpc
- Simplified class/method summaries while retaining essential information
- Streamlined comments in GrpcEndpointSource, WolverineGrpcExtensions, and base classes
- Made sample code comments more concise without losing educational value
- Condensed test documentation to focus on key testing scenarios
- Maintained important context (e.g., why WebApplication vs AlbaHost) while removing repetition
- All 29 tests pass successfully after refactoring

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…ely in cleanup commit

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
- Add end_to_end_grpc_integration.cs with actual gRPC network call tests
- Add message_bus_integration.cs for Bus.InvokeAsync/PublishAsync/SendAsync tests
- Add dependency_injection_and_services.cs for DI patterns (property vs constructor)
- Add error_handling_and_exceptions.cs for exception propagation tests

These new tests mirror patterns from Wolverine.Http.Tests and provide:
- Real gRPC client-server integration tests (not just route registration)
- Message bus operation verification
- Service resolution and DI pattern validation
- Error handling and exception propagation

Note: New end-to-end tests require HTTP/2 Kestrel configuration to run.
Existing unit tests (type discovery, bootstrapping) all pass (29 tests).

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…erns and conventions

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…traints and gotchas

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
- Configure Kestrel to use HTTP/2 via IPAddress.Loopback
- Fix abstract type discovery to exclude WolverineGrpcEndpointBase descendants
- Enable handler discovery for message bus integration tests
- 38 of 44 tests now passing (9 more than before)
- Verified proto-first and code-first samples work correctly

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Claude AI and others added 3 commits March 12, 2026 12:26
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Add code gen, add explicit proto-first gRPC docs in FAQ, update samples
@erikshafer
Copy link
Copy Markdown
Contributor Author

March-12-2026: Added code generation capabilities 🎉

  • Created GrpcChain implementing ICodeFile to generate wrapper classes around gRPC services to generate constructor injection code automatically and to match the existing HTTP handler pattern.

Claude AI and others added 15 commits March 12, 2026 14:57
- Add OpenTelemetry tracing constants and helpers for streaming operations to WolverineTracing
- Add StreamingTelemetryExtensions.WithTelemetry() for instrumenting gRPC streams
- Add IMessageBus.StreamAsync<TResponse>() API (placeholder for future full implementation)
- Streaming telemetry tracks start, message yields, completion, errors, and metrics

Benefits:
- gRPC streaming operations can now be traced with OpenTelemetry
- Lays groundwork for full streaming middleware pipeline integration
- Provides observability into stream duration, message counts, and errors

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Add test suite for StreamingTelemetryExtensions.WithTelemetry():
- Verify streaming activity creation and tagging
- Test message count and duration metrics
- Validate error handling and status codes
- Test correlation ID propagation
- Verify individual message yield events
- Test cancellation token handling

All tests pass successfully demonstrating telemetry instrumentation works correctly for gRPC streaming scenarios.

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
Add full handler pipeline support for IAsyncEnumerable<T> streaming:
- Extend IMessageInvoker interface with StreamAsync<TResponse> method
- Implement streaming execution in Executor class with telemetry
- Update MessageBus.StreamAsync to invoke handlers instead of throwing
- Add streaming support stubs to routing classes (MessageRoute, TopicRouting)
- Update NulloMessageInvoker implementations for consistency
- Add comprehensive streaming handler tests in CoreTests

This allows handlers to return IAsyncEnumerable<T> and be invoked via
IMessageBus.StreamAsync, with proper OpenTelemetry instrumentation.

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
- Fix compilation errors in StreamingHandlerTests.cs (CountRequest record syntax)
- Add streaming_invoker_behavior_tests.cs with edge cases:
  * Handler returns IAsyncEnumerable stream
  * Handler returns empty stream
  * Handler throws exception after yielding items
  * Streaming respects cancellation tokens
  * Null message throws ArgumentNullException
  * Multiple consumers can call streaming handlers

Tests validate Executor.StreamAsync behavior through IMessageBus.StreamAsync API.

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…amAsync integration

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…treamAsync pattern

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…ve NullReferenceException

Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
…erine-again' into copilot/add-grpc-support-to-wolverine-again
Co-authored-by: erikshafer <12145838+erikshafer@users.noreply.github.com>
@Blackclaws
Copy link
Copy Markdown
Contributor

Given that we've recently asked ourselves the question whether we could also use Wolverine for our gRPC api endpoints this is a welcome addition. Any clue on when it might be ready?

@erikshafer
Copy link
Copy Markdown
Contributor Author

Given that we've recently asked ourselves the question whether we could also use Wolverine for our gRPC api endpoints this is a welcome addition. Any clue on when it might be ready?

Hi @Blackclaws ! I will be refreshing this effort tomorrow, actually. @jeremydmiller recently asked what the status was and I just need a little bit more time before I got back into this. Will be tweaking a few things, along with a much-needed rebase from the main branch. I am not in a position to promise anything, but the first iteration of this has three small working samples and was working in another project!

@erikshafer
Copy link
Copy Markdown
Contributor Author

Given that we've recently asked ourselves the question whether we could also use Wolverine for our gRPC api endpoints this is a welcome addition. Any clue on when it might be ready?

A new PR and git branch will be posted shortly. Closing this PR. Apologies for the madness!

@erikshafer erikshafer closed this Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants