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
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ const config: UserConfig<DefaultTheme.Config> = {
]
},
{text: 'Partitioned Sequential Messaging', link: '/guide/messaging/partitioning'},
{text: 'Unknown Messages', link: '/guide/messaging/unknown'},
{text: 'Endpoint Specific Operations', link: '/guide/messaging/endpoint-operations'},
{text: 'Broadcast to a Specific Topic', link: '/guide/messaging/broadcast-to-topic'},
{text: 'Message Expiration', link: '/guide/messaging/expiration'},
Expand Down
30 changes: 29 additions & 1 deletion docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,32 @@ within different modules within your system, you can use [Wolverine extensions](
You can also use the `IServiceCollection.ConfigureWolverine()` method to add configuration to your
Wolverine application from outside the main `UseWolverine()` code as shown below:

snippet: sample_using_configure_wolverine
<!-- snippet: sample_using_configure_wolverine -->
<a id='snippet-sample_using_configure_wolverine'></a>
```cs
var builder = Host.CreateApplicationBuilder();

// Baseline Wolverine configuration
builder.Services.AddWolverine(opts =>
{

});

// This would be applied as an extension
builder.Services.ConfigureWolverine(w =>
{
// There is a specific helper for this, but just go for it
// as an easy example
w.Durability.Mode = DurabilityMode.Solo;
});

using var host = builder.Build();

host.Services.GetRequiredService<IWolverineRuntime>()
.Options
.Durability
.Mode
.ShouldBe(DurabilityMode.Solo);
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Configuration/using_configure_wolverine.cs#L14-L40' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_configure_wolverine' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
16 changes: 15 additions & 1 deletion docs/guide/durability/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,21 @@ migration subsystem
You also have this setting to force Wolverine to automatically "bump" and older messages that seem to be stalled in
the outbox table:

snippet: sample_configuring_outbox_stale_timeout
<!-- snippet: sample_configuring_outbox_stale_timeout -->
<a id='snippet-sample_configuring_outbox_stale_timeout'></a>
```cs
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
// Bump any persisted message in the outbox tables
// that is more than an hour old to be globally owned
// so that the durability agent can recover it and force
// it to be sent
opts.Durability.OutboxStaleTime = 1.Hours();
}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/PersistenceTests/Samples/DocumentationSamples.cs#L281-L293' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_configuring_outbox_stale_timeout' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Note that this will still respect the "deliver by" semantics. This is part of the polling that Wolverine normally does
against the inbox/outbox/node storage tables. Note that this will only happen if the setting above has a non-null
Expand Down
11 changes: 10 additions & 1 deletion docs/guide/durability/postgresql.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ as an optimization. This is not enabled by default just to avoid causing databas
migrations in a minor point release. Note that this will have some significant benefits
for inbox/outbox metrics gathering in the future:

snippet: sample_enabling_inbox_partitioning
<!-- snippet: sample_enabling_inbox_partitioning -->
<a id='snippet-sample_enabling_inbox_partitioning'></a>
```cs
var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts.Durability.EnableInboxPartitioning = true;
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/PostgresqlTests/compliance_using_table_partitioning.cs#L26-L34' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_enabling_inbox_partitioning' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## PostgreSQL Messaging Transport <Badge type="tip" text="2.5" />

Expand Down
46 changes: 43 additions & 3 deletions docs/guide/durability/sagas.md
Original file line number Diff line number Diff line change
Expand Up @@ -558,12 +558,52 @@ output was getting too verbose. `Saga` types are officially message handlers to
still use the `public static void Configure(HandlerChain)` mechanism for one off configurations to every message handler
method on the `Saga` like this:

snippet: sample_overriding_logging_on_saga
<!-- snippet: sample_overriding_logging_on_saga -->
<a id='snippet-sample_overriding_logging_on_saga'></a>
```cs
public class RevisionedSaga : Wolverine.Saga
{
// This works just the same as on any other message handler
// type
public static void Configure(HandlerChain chain)
{
chain.ProcessingLogLevel = LogLevel.None;
chain.SuccessLogLevel = LogLevel.None;
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/MartenTests/Saga/RevisionedSaga.cs#L80-L92' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_overriding_logging_on_saga' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Or if you wanted to just do it globally, something like this approach:

snippet: sample_turn_down_logging_for_sagas
<!-- snippet: sample_turn_down_logging_for_sagas -->
<a id='snippet-sample_turn_down_logging_for_sagas'></a>
```cs
public class TurnDownLoggingOnSagas : IChainPolicy
{
public void Apply(IReadOnlyList<IChain> chains, GenerationRules rules, IServiceContainer container)
{
foreach (var sagaChain in chains.OfType<SagaChain>())
{
sagaChain.ProcessingLogLevel = LogLevel.None;
sagaChain.SuccessLogLevel = LogLevel.None;
}
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/PersistenceTests/Samples/SagaChainPolicies.cs#L27-L41' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_turn_down_logging_for_sagas' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

and register that policy something like this:

snippet: sample_configuring_chain_policy_on_sagas
<!-- snippet: sample_configuring_chain_policy_on_sagas -->
<a id='snippet-sample_configuring_chain_policy_on_sagas'></a>
```cs
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts.Policies.Add<TurnDownLoggingOnSagas>();
}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/PersistenceTests/Samples/SagaChainPolicies.cs#L15-L23' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_configuring_chain_policy_on_sagas' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
2 changes: 1 addition & 1 deletion docs/guide/messaging/partitioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ opts.MessagePartitioning
});
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/MartenTests/concurrency_resilient_sharded_processing.cs#L106-L128' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_inferred_message_group_id' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Persistence/MartenTests/concurrency_resilient_sharded_processing.cs#L112-L134' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_inferred_message_group_id' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The built in rules *at this point* include:
Expand Down
23 changes: 20 additions & 3 deletions docs/guide/messaging/transports/kafka.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public static class KafkaInstrumentation
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Kafka/Wolverine.Kafka.Tests/DocumentationSamples.cs#L132-L145' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_kafkainstrumentation_middleware' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Kafka/Wolverine.Kafka.Tests/DocumentationSamples.cs#L152-L165' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_kafkainstrumentation_middleware' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Connecting to Multiple Brokers <Badge type="tip" text="4.7" />
Expand Down Expand Up @@ -229,7 +229,7 @@ using var host = await Host.CreateDefaultBuilder()
// Other configuration
}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Kafka/Wolverine.Kafka.Tests/DocumentationSamples.cs#L105-L128' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_multiple_kafka_brokers' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Kafka/Wolverine.Kafka.Tests/DocumentationSamples.cs#L125-L148' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_multiple_kafka_brokers' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Note that the `Uri` scheme within Wolverine for any endpoints from a "named" Kafka broker is the name that you supply
Expand All @@ -240,4 +240,21 @@ for the broker. So in the example above, you might see `Uri` values for `emea://
Hey, you might have an application that only consumes Kafka messages, but there are a *few* diagnostics in Wolverine that
try to send messages. To completely eliminate that, you can disable all message sending in Wolverine like this:

snippet: sample_disable_all_kafka_sending
<!-- snippet: sample_disable_all_kafka_sending -->
<a id='snippet-sample_disable_all_kafka_sending'></a>
```cs
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts
.UseKafka("localhost:9092")

// Tell Wolverine that this application will never
// produce messages to turn off any diagnostics that might
// try to "ping" a topic and result in errors
.ConsumeOnly();

}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Kafka/Wolverine.Kafka.Tests/DocumentationSamples.cs#L105-L120' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_disable_all_kafka_sending' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
108 changes: 108 additions & 0 deletions docs/guide/messaging/unknown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Unknown Messages

When Wolverine receives a message from the outside world, it's keying off the [message type name](/guide/messages.html#message-type-name-or-alias) from the `Envelope` to
"know" what message type it's receiving and therefore, which handler(s) to execute. It's an imperfect world of course,
so it's perfectly possible that your system will receive a message from the outside world with a message type name that
your system does not recognize.

Out of the box Wolverine will simply log that it received an unknown message type and discard the message, but there are
means to take additional actions on "missing handler" messages where Wolverine does not recognize the message type.

## Move to the Dead Letter Queue <Badge type="tip" text="5.3" />

You can declaratively tell Wolverine to persist every message received with an unknown message type name
to the dead letter queue with this flag:

<!-- snippet: sample_unknown_messages_go_to_dead_letter_queue -->
<a id='snippet-sample_unknown_messages_go_to_dead_letter_queue'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
var connectionString = builder.Configuration.GetConnectionString("rabbit");
opts.UseRabbitMq(connectionString).UseConventionalRouting();

// All unknown message types received should be placed into
// the proper dead letter queue mechanism
opts.UnknownMessageBehavior = UnknownMessageBehavior.DeadLetterQueue;
});
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/moving_unknown_message_type_to_dlq.cs#L23-L36' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_unknown_messages_go_to_dead_letter_queue' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The message will be moved to the dead letter queue mechanism for the listening endpoint where the message was received.

## Custom Actions

::: note
The missing handlers are additive, meaning that you can provide more than one and Wolverine will try to execute
each one that is registered for the missing handler behavior.
:::

You can direct Wolverine to take custom actions on messages received with unknown message type names by providing
a custom implementation of this interface:

<!-- snippet: sample_IMissingHandler -->
<a id='snippet-sample_imissinghandler'></a>
```cs
namespace Wolverine;

/// <summary>
/// Hook interface to receive notifications of envelopes received
/// that do not match any known handlers within the system
/// </summary>
public interface IMissingHandler
{
/// <summary>
/// Executes for unhandled envelopes
/// </summary>
/// <param name="context"></param>
/// <param name="root"></param>
/// <returns></returns>
ValueTask HandleAsync(IEnvelopeLifecycle context, IWolverineRuntime root);
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Wolverine/IMissingHandler.cs#L4-L22' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_imissinghandler' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Here's a made up sample that theoretically posts a message to a Slack room by sending a Wolverine message in response:

<!-- snippet: sample_MyCustomActionForMissingHandlers -->
<a id='snippet-sample_mycustomactionformissinghandlers'></a>
```cs
public class MyCustomActionForMissingHandlers : IMissingHandler
{
public ValueTask HandleAsync(IEnvelopeLifecycle context, IWolverineRuntime root)
{
var bus = new MessageBus(root);
return bus.PublishAsync(new PostInSlack("Incidents",
$"Got an unknown message with type '{context.Envelope.MessageType}' and id {context.Envelope.Id}"));
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/MissingHandlerSample.cs#L10-L22' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_mycustomactionformissinghandlers' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

And simply registering that with your application's IoC container against the `IMissingHandler` interface like this:

<!-- snippet: sample_registering_custom_missing_handler -->
<a id='snippet-sample_registering_custom_missing_handler'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// configuration
opts.UnknownMessageBehavior = UnknownMessageBehavior.DeadLetterQueue;
});

builder.Services.AddSingleton<IMissingHandler, MyCustomActionForMissingHandlers>();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/MissingHandlerSample.cs#L28-L39' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_registering_custom_missing_handler' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Tracked Session Testing

Just know that the [Tracked Session](/guide/testing.html#integration-testing-with-tracked-sessions) subsystem for integration
testing exposes a separate record collection for `NoHandlers` and reports when that happens through its output for hopefully
easy troubleshooting on test failures.

2 changes: 1 addition & 1 deletion docs/tutorials/interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public class OurKafkaJsonMapper<TMessage> : IKafkaEnvelopeMapper
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Kafka/Wolverine.Kafka.Tests/DocumentationSamples.cs#L147-L183' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ourkafkajsonmapper' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Kafka/Wolverine.Kafka.Tests/DocumentationSamples.cs#L167-L203' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ourkafkajsonmapper' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Which is essentially how the built in "Raw JSON" mapper works in external transport mappers. In the envelope mapper above
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Data.Common;
using Weasel.Core;
using Wolverine.Persistence.Durability;
using Wolverine.Runtime.Interop;
using Wolverine.Transports;
using DbCommandBuilder = Weasel.Core.DbCommandBuilder;

Expand Down
42 changes: 42 additions & 0 deletions src/Samples/DocumentationSamples/MissingHandlerSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Wolverine;
using Wolverine.Runtime;

namespace DocumentationSamples;

public record PostInSlack(string Room, string Message);

#region sample_MyCustomActionForMissingHandlers

public class MyCustomActionForMissingHandlers : IMissingHandler
{
public ValueTask HandleAsync(IEnvelopeLifecycle context, IWolverineRuntime root)
{
var bus = new MessageBus(root);
return bus.PublishAsync(new PostInSlack("Incidents",
$"Got an unknown message with type '{context.Envelope.MessageType}' and id {context.Envelope.Id}"));
}
}

#endregion

public static class ConfigureMissingHandlers
{
public static async Task configure()
{
#region sample_registering_custom_missing_handler

var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// configuration
opts.UnknownMessageBehavior = UnknownMessageBehavior.DeadLetterQueue;
});

builder.Services.AddSingleton<IMissingHandler, MyCustomActionForMissingHandlers>();

#endregion
}
}

Loading
Loading