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
14 changes: 12 additions & 2 deletions docs/guide/messaging/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,21 @@ builder.Host.UseWolverine(opts =>

## Listening Endpoint Configuration

Once you've connected Wolverine to your external infrastructure, you'll want to configure listening endpoints so
that Wolverine can receive and process incoming messages. Listening endpoints control which queues, topics, or
subscriptions Wolverine reads from, how many messages are processed in parallel, and how received messages are
buffered or made durable.

## Sending Endpoint Configuration

See [Listener Configuration](/guide/messaging/listeners) for the full set of options, including parallelism,
durability, and endpoint-level error handling.

## Sending Endpoint Configuration

To publish messages out through external infrastructure, you'll configure sending endpoints and the routing
rules that teach Wolverine where each message type should be sent.

See [Subscriptions and Message Routing](/guide/messaging/subscriptions) for how to declare those rules, and
[The Message Bus](/guide/messaging/message-bus) for the `IMessageBus` API you use to publish, send, and invoke
messages from your application code.


25 changes: 24 additions & 1 deletion docs/guide/messaging/message-bus.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,30 @@ public static (Order, OrderTimeout) Start(StartOrder order, ILogger<Order> logge

## Customizing Message Delivery

TODO -- more text here. NEW PAGE???
When you publish or send a message you can optionally pass a `DeliveryOptions` object to override how — and
when — that particular message is delivered. Every overload of `IMessageBus.PublishAsync()` and
`IMessageBus.SendAsync()` accepts an optional `DeliveryOptions` argument, so you can tailor a single message
without changing any endpoint-wide configuration.

`DeliveryOptions` lets you control things like:

* **Custom headers** — add arbitrary string metadata that travels with the message, either through the
`Headers` dictionary or the fluent `WithHeader(key, value)` method.
* **Expiration** — `DeliverBy` (an absolute time) or the `DeliverWithin` convenience setter (a `TimeSpan` from
now) tell Wolverine to discard the message if it hasn't been delivered and processed in time. See
[Message Expiration](/guide/messaging/expiration).
* **Scheduled delivery** — `ScheduledTime` (an absolute time) or `ScheduleDelay` (a `TimeSpan` from now) defer
processing until later.
* **Request/response** — set `ResponseType` (or use the static `DeliveryOptions.RequireResponse<T>()` helper) to
ask the receiver to send a reply message of that type back to you.
* **Tenancy and tracing** — override the `TenantId`, `CorrelationId`, or `CausationId` stamped onto the outgoing
envelope for just this message.
* **Acknowledgements** — set `AckRequested` to request an acknowledgement from the receiver.
* **Transport-specific routing** — `GroupId` (AMQP 1.0 group id / Azure Service Bus session id / Amazon SQS FIFO
`MessageGroupId`), `DeduplicationId` (Amazon SQS FIFO), and `PartitionKey` (Kafka) map onto the corresponding
native broker concepts.

Here's an example exercising several of these options at once:

<!-- snippet: sample_sendmessageswithdeliveryoptions -->
<a id='snippet-sample_sendmessageswithdeliveryoptions'></a>
Expand Down
110 changes: 110 additions & 0 deletions docs/guide/messaging/policies.md
Original file line number Diff line number Diff line change
@@ -1 +1,111 @@
# Endpoint Policies

An *endpoint policy* is a reusable rule that Wolverine applies to every messaging endpoint (listeners, senders,
and local queues) as your application bootstraps. Rather than configuring each Rabbit MQ queue or Azure Service
Bus subscription one at a time, you can write a single policy that's evaluated against *all* endpoints —
including ones created by [conventional routing](/guide/messaging/subscriptions) that you never explicitly
declared.

Endpoint policies are how a lot of Wolverine's own behavior is implemented. For example, the Rabbit MQ
`UseQuorumQueues()` helper is just an endpoint policy that flips every application-owned queue to a quorum queue.

## Writing an Endpoint Policy

Implement the `Wolverine.Configuration.IEndpointPolicy` interface. It has a single method that receives each
`Endpoint` together with the live `IWolverineRuntime`:

<!-- snippet: sample_custom_endpoint_policy -->
<a id='snippet-sample_custom_endpoint_policy'></a>
```cs
// Force every application listening endpoint to process messages inline.
// Wolverine's own internal/system endpoints are left alone.
public class InlineListenersPolicy : IEndpointPolicy
{
public void Apply(Endpoint endpoint, IWolverineRuntime runtime)
{
// Don't touch endpoints that Wolverine itself owns
if (endpoint.Role == EndpointRole.System) return;

if (endpoint.IsListener)
{
endpoint.Mode = EndpointMode.Inline;
}
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/EndpointPolicySamples.cs#L8-L26' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_custom_endpoint_policy' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

A few things worth knowing about the `Endpoint` you're handed:

* `endpoint.Role` is either `EndpointRole.System` (something Wolverine created and owns internally) or
`EndpointRole.Application` (something you declared, or that conventional routing created on your behalf). It's
almost always correct to skip `System` endpoints in your policy.
* `endpoint.Mode` controls the durability/buffering behavior and is one of `EndpointMode.Durable`,
`EndpointMode.BufferedInMemory`, or `EndpointMode.Inline`.
* `endpoint.IsListener` tells you whether the endpoint is receiving messages.
* `endpoint.Uri` is the unique address of the endpoint, which is handy for matching by transport scheme or queue
name.

Your policy runs once per endpoint during bootstrapping, so it's the right place for cross-cutting configuration
but *not* for per-message logic.

## Registering a Policy

Policies are registered through `opts.Policies` inside `UseWolverine()`. You can add a policy type that will be
created by Wolverine, or add a pre-built instance:

<!-- snippet: sample_register_endpoint_policy -->
<a id='snippet-sample_register_endpoint_policy'></a>
```cs
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
// Register a custom IEndpointPolicy type
opts.Policies.Add<InlineListenersPolicy>();

// Or register a policy inline with LambdaEndpointPolicy<T>.
// The lambda only runs for endpoints assignable to T (here, every Endpoint)
opts.Policies.Add(new LambdaEndpointPolicy<Endpoint>((endpoint, runtime) =>
{
if (endpoint.Role == EndpointRole.System) return;
endpoint.Mode = EndpointMode.BufferedInMemory;
}));
}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/DocumentationSamples/EndpointPolicySamples.cs#L32-L49' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_register_endpoint_policy' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

When you only need to react to one kind of endpoint (say, just Rabbit MQ queues), the generic
`LambdaEndpointPolicy<T>` is the most convenient option. The supplied lambda is only invoked for endpoints that
are assignable to `T`. This is exactly how Wolverine's Rabbit MQ integration implements `UseQuorumQueues()`:

```cs
// From Wolverine's own Rabbit MQ integration
Options.Policies.Add(new LambdaEndpointPolicy<RabbitMqQueue>((queue, _) =>
{
if (queue.Role == EndpointRole.Application)
{
queue.QueueType = QueueType.quorum;
}
}));
```

## Built-in Endpoint Policy Helpers

You don't always need to write your own `IEndpointPolicy`. For the most common cases, `opts.Policies` already
exposes convenience methods that build endpoint policies for you. Each one automatically skips Wolverine's
internal `System` endpoints:

* `opts.Policies.AllListeners(x => ...)` — apply a `ListenerConfiguration` action to every (non-local) listening
endpoint.
* `opts.Policies.AllSenders(x => ...)` — apply an `ISubscriberConfiguration` action to every sending endpoint.
* `opts.Policies.AllLocalQueues(x => ...)` — apply configuration to every local queue.

For example:

```cs
opts.Policies.AllListeners(x => x.MaximumParallelMessages(5));
```

See [Configuring Listeners](/guide/messaging/listeners) for more on what's available through these helpers.
45 changes: 30 additions & 15 deletions docs/guide/messaging/transports/azureservicebus/deadletterqueues.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,52 @@ For inline endpoints, Wolverine uses native [Azure Service Bus dead letter queue

To configure an endpoint for inline processing:

<!-- snippet-todo: sample_asb_inline_dlq -->
<!-- snippet: sample_asb_inline_dlq -->
<a id='snippet-sample_asb_inline_dlq'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// One way or another, you're probably pulling the Azure Service Bus
// connection string out of configuration
var azureServiceBusConnectionString = builder
.Configuration
.GetConnectionString("azure-service-bus");
.GetConnectionString("azure-service-bus")!;

// Connect to the broker
opts.UseAzureServiceBus(azureServiceBusConnectionString).AutoProvision();

// Use inline processing with native Azure Service Bus DLQ
// Inline endpoints use Azure Service Bus's *native* dead letter
// subqueue of the source queue. There's no Wolverine inbox, so
// dead lettering is handled entirely by Azure Service Bus.
opts.ListenToAzureServiceBusQueue("inline-queue")
.ProcessInline();
});

using var host = builder.Build();
await host.StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Azure/Wolverine.AzureServiceBus.Tests/DocumentationSamples.cs#L528-L549' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_asb_inline_dlq' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

### Buffered Endpoints

For buffered endpoints, Wolverine sends failed messages to a designated dead letter queue. By default, this queue is named `wolverine-dead-letter-queue`.

To customize the dead letter queue for buffered endpoints:

<!-- snippet-todo: sample_asb_buffered_dlq -->
<!-- snippet: sample_asb_buffered_dlq -->
<a id='snippet-sample_asb_buffered_dlq'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
var azureServiceBusConnectionString = builder
.Configuration
.GetConnectionString("azure-service-bus");
.GetConnectionString("azure-service-bus")!;

opts.UseAzureServiceBus(azureServiceBusConnectionString).AutoProvision();

// Customize the dead letter queue name for buffered endpoint
// Buffered endpoints move failed messages to a Wolverine-managed
// dead letter queue. The default name is "wolverine-dead-letter-queue",
// but you can override it per endpoint.
opts.ListenToAzureServiceBusQueue("buffered-queue")
.BufferedInMemory()
.ConfigureDeadLetterQueue("my-custom-dlq");
Expand All @@ -57,25 +62,29 @@ builder.UseWolverine(opts =>
using var host = builder.Build();
await host.StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Azure/Wolverine.AzureServiceBus.Tests/DocumentationSamples.cs#L554-L576' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_asb_buffered_dlq' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

### Durable Endpoints

Durable endpoints behave similarly to buffered endpoints, with dead lettering to the configured dead letter queue, while leveraging Wolverine's persistence for reliability.

To customize the dead letter queue for durable endpoints:

<!-- snippet-todo: sample_asb_durable_dlq -->
<!-- snippet: sample_asb_durable_dlq -->
<a id='snippet-sample_asb_durable_dlq'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
var azureServiceBusConnectionString = builder
.Configuration
.GetConnectionString("azure-service-bus");
.GetConnectionString("azure-service-bus")!;

opts.UseAzureServiceBus(azureServiceBusConnectionString).AutoProvision();

// Customize the dead letter queue name for durable endpoint
// Durable endpoints behave like buffered endpoints for dead lettering,
// but add Wolverine's durable inbox persistence for reliability.
opts.ListenToAzureServiceBusQueue("durable-queue")
.UseDurableInbox()
.ConfigureDeadLetterQueue("my-custom-dlq");
Expand All @@ -84,27 +93,33 @@ builder.UseWolverine(opts =>
using var host = builder.Build();
await host.StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Azure/Wolverine.AzureServiceBus.Tests/DocumentationSamples.cs#L581-L602' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_asb_durable_dlq' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Disabling Dead Letter Queues

You can disable dead letter queuing for specific endpoints if needed:

<!-- snippet-todo: sample_disable_asb_dlq -->
<!-- snippet: sample_disable_asb_dlq -->
<a id='snippet-sample_disable_asb_dlq'></a>
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
var azureServiceBusConnectionString = builder
.Configuration
.GetConnectionString("azure-service-bus");
.GetConnectionString("azure-service-bus")!;

opts.UseAzureServiceBus(azureServiceBusConnectionString).AutoProvision();

// Disable dead letter queuing for this endpoint
// Disable Wolverine-managed dead letter queueing for this endpoint.
// Failed messages fall back to Wolverine's regular error handling.
opts.ListenToAzureServiceBusQueue("no-dlq")
.DisableDeadLetterQueueing();
});

using var host = builder.Build();
await host.StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/Azure/Wolverine.AzureServiceBus.Tests/DocumentationSamples.cs#L607-L627' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_disable_asb_dlq' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
Loading
Loading