Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/grpc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: grpc

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:

env:
config: Release
disable_test_parallelization: true

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup .NET 8
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

- name: Setup .NET 9
uses: actions/setup-dotnet@v5
with:
dotnet-version: 9.0.x

- name: Setup .NET 10
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x

- name: Run gRPC Tests
run: ./build.sh CIGrpc --framework net9.0
11 changes: 11 additions & 0 deletions build/CITargets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,17 @@ void BuildTestProjectsWithFramework(string frameworkOverride, params AbsolutePat
RunSingleProjectOneClassAtATime(leaderElectionTests, frameworkOverride: "net9.0");
});

Target CIGrpc => _ => _
.ProceedAfterFailure()
.Executes(() =>
{
var tests = RootDirectory / "src" / "Wolverine.Grpc.Tests" / "Wolverine.Grpc.Tests.csproj";

BuildTestProjects(tests);

RunSingleProjectOneClassAtATime(tests);
});

Target CIAzureServiceBus => _ => _
.ProceedAfterFailure()
.Executes(() =>
Expand Down
1 change: 1 addition & 0 deletions build/build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ partial class Build : NukeBuild
Solution.Transports.Redis.Wolverine_Redis,
Solution.Transports.SignalR.Wolverine_SignalR,
Solution.Transports.NATS.Wolverine_Nats,
Solution.Grpc.Wolverine_Grpc,
Solution.Persistence.EFCore.Wolverine_EntityFrameworkCore,
Solution.Persistence.Polecat.Wolverine_Polecat
};
Expand Down
8 changes: 7 additions & 1 deletion docs/guide/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ your message handlers. Wolverine's [middleware strategy](/guide/handlers/middlew
middleware directly into the runtime pipeline without requiring the copious usage of adapter interfaces
that is prevalent in most other .NET frameworks.

::: info
This page covers Wolverine-specific use of code generation. The shared JasperFx code-generation library that backs it — [frames](https://shared-libs.jasperfx.net/codegen/frames.html), [variables](https://shared-libs.jasperfx.net/codegen/variables.html), [`MethodCall`](https://shared-libs.jasperfx.net/codegen/method-call.html), [generated types](https://shared-libs.jasperfx.net/codegen/generated-types.html), and the [`codegen` CLI command](https://shared-libs.jasperfx.net/codegen/cli.html) — is documented at [shared-libs.jasperfx.net/codegen](https://shared-libs.jasperfx.net/codegen/). Reach for it when you're authoring a custom `IVariableSource` or middleware frame.
:::

That's great when everything is working as it should, but there's a couple issues:

1. The usage of the Roslyn compiler at runtime *can sometimes be slow* on its first usage. This can lead to sluggish *cold start*
Expand Down Expand Up @@ -301,7 +305,7 @@ JasperFx).

::: tip
All of these commands are from the JasperFx.CodeGeneration.Commands library that Wolverine adds as
a dependency. This is shared with [Marten](https://martendb.io) as well.
a dependency. This is shared with [Marten](https://martendb.io) as well. See the [`codegen` CLI reference](https://shared-libs.jasperfx.net/codegen/cli.html) for every subcommand and flag.
:::

To preview the generated source code, use this command line usage from the root directory of your .NET project:
Expand Down Expand Up @@ -401,6 +405,8 @@ For Console applications or non-standard project structures, you may need to cus

### Using CritterStackDefaults

`CritterStackDefaults` is the shared entry point for opinionated defaults across the Critter Stack (Wolverine, Marten, Polecat, …). Full reference: [shared-libs.jasperfx.net/configuration/critter-stack-defaults](https://shared-libs.jasperfx.net/configuration/critter-stack-defaults.html).

You can configure the output path globally for all Critter Stack tools:

<!-- snippet: sample_configure_generated_code_output_path -->
Expand Down
4 changes: 4 additions & 0 deletions docs/guide/command-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ With help from its [JasperFx](https://github.com/JasperFx) team mate [Oakton](ht
tools. To get started, apply Oakton as the command line parser in your applications as shown in the last line of code in this
sample application bootstrapping from Wolverine's [Getting Started](/tutorials/getting-started):

::: info
This page covers the Wolverine-specific CLI surface. The underlying JasperFx command-line library that backs `RunJasperFxCommands` — including how to [author your own commands](https://shared-libs.jasperfx.net/cli/writing-commands.html), [argument/flag handling](https://shared-libs.jasperfx.net/cli/arguments-flags.html), and [environment checks](https://shared-libs.jasperfx.net/cli/environment-checks.html) — is documented at [shared-libs.jasperfx.net/cli](https://shared-libs.jasperfx.net/cli/). When a command accepts a generic flag not listed on this page, that's where to look first.
:::

<!-- snippet: sample_quickstart_program -->
<a id='snippet-sample_quickstart_program'></a>
```cs
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/durability/efcore/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@ dotnet run -- resources list
dotnet run -- resources clear
```

These commands manage both Wolverine's internal tables and your EF Core entity tables together.
These commands manage both Wolverine's internal tables and your EF Core entity tables together. For the finer-grained Weasel commands (`db-apply`, `db-assert`, `db-patch`, `db-dump`, `db-list`) — useful for CI deploy gates and exporting DDL — see the [Weasel CLI reference](https://weasel.jasperfx.net/cli/).
13 changes: 12 additions & 1 deletion docs/guide/durability/managing.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,24 @@ The available commands are:
storage Administer the envelope storage
```

There's admittedly some duplication here with different options coming from [Oakton](https://jasperfx.github.io/oakton) itself, the [Weasel.CommandLine](https://weasel.jasperfx.net/) library,
There's admittedly some duplication here with different options coming from [Oakton](https://jasperfx.github.io/oakton) itself, the [Weasel.CommandLine](https://weasel.jasperfx.net/cli/) library,
and the `storage` command from Wolverine itself. To build out the schema objects for [message persistence](/guide/durability/), you
can use this command to apply any outstanding database changes necessary to bring the database schema to the Wolverine configuration:

```bash
dotnet run -- db-apply
```

::: info
The `db-apply`, `db-assert`, `db-patch`, `db-dump`, and `db-list` commands come from Weasel. See the per-command references at:

- [`db-apply`](https://weasel.jasperfx.net/cli/db-apply.html) — apply all outstanding changes to the configured database(s)
- [`db-assert`](https://weasel.jasperfx.net/cli/db-assert.html) — assert the live schema matches the configuration (good for CI deploy gates)
- [`db-patch`](https://weasel.jasperfx.net/cli/db-patch.html) — emit a SQL patch + rollback file for pending changes
- [`db-dump`](https://weasel.jasperfx.net/cli/db-dump.html) — dump the full DDL for the configured database(s)
- [`db-list`](https://weasel.jasperfx.net/cli/db-list.html) — list configured databases
:::

> NOTE: See the [Exporting SQL Scripts](#exporting-sql-scripts) section down the page for details of applying migrations when integrating with Marten

or this option -- but just know that this will also clear out any existing message data:
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/handlers/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ on an individual handler method or apply to all handler methods on the same hand

## Custom Code Generation

For more advanced usage, you can drop down to the JasperFx.CodeGeneration `Frame` model to directly inject code.
For more advanced usage, you can drop down to the JasperFx.CodeGeneration `Frame` model to directly inject code. The `Frame` model itself — plus `Variable`, `MethodCall`, and the built-in frames Wolverine composes with — is documented in depth at [shared-libs.jasperfx.net/codegen](https://shared-libs.jasperfx.net/codegen/).

The first step is to create a JasperFx.CodeGeneration `Frame` class that generates that code around the inner message or HTTP handler:

Expand Down
33 changes: 33 additions & 0 deletions src/Wolverine.Grpc.Tests/GrpcTransportCompliance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Wolverine.ComplianceTests.Compliance;
using Wolverine.Grpc;
using Xunit;

public class GrpcComplianceFixture : TransportComplianceFixture, IAsyncLifetime
{
public const int ReceiverPort = 5150;
public const int SenderPort = 5151;

public GrpcComplianceFixture() : base(new Uri($"grpc://localhost:{ReceiverPort}"), 30)
{
}

public async Task InitializeAsync()
{
OutboundAddress = new Uri($"grpc://localhost:{ReceiverPort}");

await ReceiverIs(opts =>
{
opts.ListenAtGrpcPort(ReceiverPort);
});

await SenderIs(opts =>
{
opts.ListenAtGrpcPort(SenderPort).UseForReplies();
opts.PublishAllMessages().ToGrpcEndpoint("localhost", ReceiverPort);
});
}

public new Task DisposeAsync() => Task.CompletedTask;
}

public class GrpcTransportCompliance : TransportCompliance<GrpcComplianceFixture>;
1 change: 1 addition & 0 deletions src/Wolverine.Grpc.Tests/Wolverine.Grpc.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
<ProjectReference Include="..\Wolverine.Grpc\Wolverine.Grpc.csproj"/>
<ProjectReference Include="..\Testing\Wolverine.ComplianceTests\Wolverine.ComplianceTests.csproj"/>
<ProjectReference Include="..\Extensions\Wolverine.FluentValidation\Wolverine.FluentValidation.csproj"/>
<ProjectReference Include="..\Extensions\Wolverine.FluentValidation.Grpc\Wolverine.FluentValidation.Grpc.csproj"/>
</ItemGroup>
Expand Down
37 changes: 37 additions & 0 deletions src/Wolverine.Grpc/GrpcEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.Extensions.Logging;
using Wolverine.Configuration;
using Wolverine.Grpc.Internals;
using Wolverine.Runtime;
using Wolverine.Transports;
using Wolverine.Transports.Sending;

namespace Wolverine.Grpc;

public class GrpcEndpoint : Endpoint
{
public GrpcEndpoint(Uri uri) : base(uri, EndpointRole.Application)
{
Host = uri.Host;
Port = uri.IsDefaultPort ? 5000 : uri.Port;
}

public string Host { get; }
public int Port { get; }

public override async ValueTask<IListener> BuildListenerAsync(IWolverineRuntime runtime, IReceiver receiver)
{
var listener = new GrpcListener(Uri, Port, receiver, runtime.LoggerFactory.CreateLogger<GrpcListener>());
await listener.StartAsync();
return listener;
}

protected override ISender CreateSender(IWolverineRuntime runtime)
{
return new GrpcSender(Uri, Host, Port, runtime.LoggerFactory.CreateLogger<GrpcSender>());
}

protected override bool supportsMode(EndpointMode mode)
{
return mode is EndpointMode.Inline or EndpointMode.BufferedInMemory;
}
}
10 changes: 10 additions & 0 deletions src/Wolverine.Grpc/GrpcListenerConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Wolverine.Configuration;

namespace Wolverine.Grpc;

public class GrpcListenerConfiguration : ListenerConfiguration<GrpcListenerConfiguration, GrpcEndpoint>
{
public GrpcListenerConfiguration(GrpcEndpoint endpoint) : base(endpoint)
{
}
}
10 changes: 10 additions & 0 deletions src/Wolverine.Grpc/GrpcSubscriberConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Wolverine.Configuration;

namespace Wolverine.Grpc;

public class GrpcSubscriberConfiguration : SubscriberConfiguration<GrpcSubscriberConfiguration, GrpcEndpoint>
{
public GrpcSubscriberConfiguration(GrpcEndpoint endpoint) : base(endpoint)
{
}
}
42 changes: 42 additions & 0 deletions src/Wolverine.Grpc/GrpcTransport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using JasperFx.Core;
using Wolverine.Configuration;
using Wolverine.Runtime;
using Wolverine.Transports;

namespace Wolverine.Grpc;

public class GrpcTransport : TransportBase<GrpcEndpoint>
{
private readonly LightweightCache<Uri, GrpcEndpoint> _endpoints;

public GrpcTransport() : base("grpc", "gRPC Transport", [])
{
_endpoints = new LightweightCache<Uri, GrpcEndpoint>(uri => new GrpcEndpoint(uri));
}

protected override IEnumerable<GrpcEndpoint> endpoints() => _endpoints;

protected override GrpcEndpoint findEndpointByUri(Uri uri) => _endpoints[uri];

public GrpcEndpoint EndpointForLocalPort(int port)
{
var uri = new Uri($"grpc://localhost:{port}");
return _endpoints[uri];
}

public GrpcEndpoint EndpointFor(string host, int port)
{
var uri = new Uri($"grpc://{host}:{port}");
return _endpoints[uri];
}

public override ValueTask InitializeAsync(IWolverineRuntime runtime)
{
foreach (var endpoint in _endpoints)
{
endpoint.Compile(runtime);
}

return ValueTask.CompletedTask;
}
}
34 changes: 34 additions & 0 deletions src/Wolverine.Grpc/GrpcTransportExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using JasperFx.Core.Reflection;
using Wolverine.Configuration;

namespace Wolverine.Grpc;

public static class GrpcTransportExtensions
{
/// <summary>
/// Configure Wolverine to listen for gRPC messages on the specified port.
/// The listener starts an embedded gRPC server bound to all interfaces.
/// </summary>
public static GrpcListenerConfiguration ListenAtGrpcPort(this WolverineOptions options, int port)
{
var transport = options.Transports.GetOrCreate<GrpcTransport>();
var endpoint = transport.EndpointForLocalPort(port);
endpoint.IsListener = true;
return new GrpcListenerConfiguration(endpoint);
}

/// <summary>
/// Publish messages to the specified gRPC endpoint (host:port).
/// </summary>
public static GrpcSubscriberConfiguration ToGrpcEndpoint(
this IPublishToExpression publishing,
string host,
int port)
{
var transports = publishing.As<PublishingExpression>().Parent.Transports;
var transport = transports.GetOrCreate<GrpcTransport>();
var endpoint = transport.EndpointFor(host, port);
publishing.To(endpoint.Uri);
return new GrpcSubscriberConfiguration(endpoint);
}
}
Loading