Skip to content

Commit

Permalink
Improved routing documentation and the new Separated handler mode. Cl…
Browse files Browse the repository at this point in the history
…oses GH-1165. Closes GH-1130. Closes GH-1087. Closes GH-1050

Adjusting the mechanism for how "sticky" message routing works so that you can have a mix of sticky and not sticky local handlers. Ick.

New MultipleHandlerBehavior.Separated flag for "automatic sticky" handlers

Ability to make conventional local routing be additive with external message brokers to

External transport routing conventions are "sticky handler" aware now
  • Loading branch information
jeremydmiller committed Jan 10, 2025
1 parent df3bd32 commit ff73eae
Show file tree
Hide file tree
Showing 35 changed files with 1,286 additions and 61 deletions.
2 changes: 1 addition & 1 deletion docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const config: UserConfig<DefaultTheme.Config> = {
items: [
{text: 'Introduction to Messaging', link: '/guide/messaging/introduction'},
{text: 'Sending Messages', link: '/guide/messaging/message-bus'},
{text: 'Subscriptions & Message Routing', link: '/guide/messaging/subscriptions'},
{text: 'Message Routing', link: '/guide/messaging/subscriptions'},
{text: 'Listening Endpoints', link: '/guide/messaging/listeners'},
{
text: 'Transports',
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/durability/efcore.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,4 @@ side effects translates to an EF Core `DbContext` update.

## Entity Attribute Loading

The EF Core integration is able to completely support the [Entity attribute usage](/guide/handlers/persistence.html#automatically-loading-entities-to-method-parameters).
The EF Core integration is able to completely support the [Entity attribute usage](/guide/handlers/persistence.html#automatically-loading-entities-to-method-parameters).
63 changes: 62 additions & 1 deletion docs/guide/handlers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public class CreateProjectHandler(IProjectRepository Repository)
## Rules for Message Handlers

::: info
The naming conventions in Wolverine are descended from a much earlier tool and the exact origins of
The naming conventions in Wolverine are descended from a much earlier tool (FubuTransportation circa 2013, which was in turn meant to replace and even older tool called Rhino Service Bus) and the exact origins of
the particular names are lost in the mist of time
:::

Expand All @@ -159,6 +159,67 @@ Also see [stateful sagas](/guide/durability/sagas) as they have some additional
See [return values](./return-values) for much more information about what types can be returned from a handler method and how Wolverine
would use those values.

## Multiple Handlers for the Same Message Type <Badge type="tip" text="3.6" />

::: tip
Pay attention to this section if you are trying to utilize a "Modular Monolith" architecture.
:::

Let's say that you want to take more than one action on a message type published in or to your
application. In this case you'll probably have more than one handler method for the exact same
message type. **The original concept for Wolverine was to effectively combine these individual handlers
into one logical handler that executes together, and in the same logical transaction (if you use transactional middleware).**

This very old decision has turned out to be harmful for folks trying to use Wolverine with newer ideas about "Modular Monolith"
architectures or "Event Driven Architecture" approaches where you much more frequently take completely independent actions
on the same message.

To alleviate this issue of combining handlers, Wolverine first introduced the [Sticky Handler concept](/guide/handlers/sticky) in Wolverine 3.0
where you're able to explicitly separate handlers for the same message type and "stick" them against different listening endpoints or local queues.

Now though, you can flip this switch in one place to ensure that Wolverine always "separates" handlers for the same message type
into completely separate Wolverine message handlers and message subscriptions:

<!-- snippet: sample_using_MultipleHandlerBehavior -->
<a id='snippet-sample_using_multiplehandlerbehavior'></a>
```cs
using var host = Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
// Right here, tell Wolverine to make every handler "sticky"
opts.MultipleHandlerBehavior = MultipleHandlerBehavior.Separated;
}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/MessageRoutingTests/using_separate_handlers.cs#L13-L22' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_multiplehandlerbehavior' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

This makes a couple changes. For example, let's say that you have these handlers for the same message type of `MyApp.Orders.OrderCreated`:

1. `MyApp.Module1.OrderCreatedHandler`
2. `MyApp.Module2.OrderCreatedHandler`
3. `MyApp.Module3.OrderCreatedHandler`

In the default `ClassicCombineIntoOneLogicalHandler` mode, Wolverine will combine all of those handlers into one logical
handler that would be published (using default routing configuration) to a local queue named "MyApp.Orders.OrderCreated".
By switching to the `Separated` mode, Wolverine will create three completely separate handlers and local subscriptions
named:

1. "MyApp.Module1.OrderCreatedHandler" with only executes the handler with the same full name
2. "MyApp.Module2.OrderCreatedHandler" with only executes the handler with the same full name
3. "MyApp.Module3.OrderCreatedHandler" with only executes the handler with the same full name

Likewise, if you were using conventional routing for an external message broker, using the `Separated` mode
will create separate listeners for each individual handler type and key the naming off of the handler type.
So if you were using the baseline Rabbit MQ conventions and `Separated`, you would end up with three
Rabbit MQ queues that each had a "sticky" relationship to one particular handler like so:

1. Listening to a queue named "MyApp.Module1.OrderCreatedHandler" that executes the `MyApp.Module1.OrderCreatedHandler` handler
2. And you get the picture...

In all cases, if you are using one of the built in message broker conventional routing approaches, Wolverine will create
a separate listener for each handler using the handler type to determine the queue / subscription / topic names instead
of the message type *if* there is more than one handler for that type.

## Message Handler Parameters

::: info
Expand Down
8 changes: 4 additions & 4 deletions docs/guide/handlers/sticky.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ message as an input.
```cs
public class StickyMessage;
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/sticky_message_handlers.cs#L240-L244' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_stickymessage' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/sticky_message_handlers.cs#L218-L222' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_stickymessage' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

And we're going to handle that `StickyMessage` message separately with two different handler types:
Expand All @@ -51,7 +51,7 @@ public static class GreenStickyHandler
}
}
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/sticky_message_handlers.cs#L246-L266' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_sticky_handler_attribute' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/sticky_message_handlers.cs#L224-L244' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_sticky_handler_attribute' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

::: tip
Expand Down Expand Up @@ -79,7 +79,7 @@ using var host = await Host.CreateDefaultBuilder()
opts.ListenAtPort(4000).Named("blue");
}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/sticky_message_handlers.cs#L178-L188' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_named_listener_endpoint' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/sticky_message_handlers.cs#L156-L166' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_named_listener_endpoint' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

With all of that being said, the end result of the two `StickyMessage` handlers that are marked with `[StickyHandler]`
Expand Down Expand Up @@ -119,7 +119,7 @@ using var host = await Host.CreateDefaultBuilder()

}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/sticky_message_handlers.cs#L193-L211' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_sticky_handlers_by_endpoint_with_fluent_interface' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/CoreTests/Acceptance/sticky_message_handlers.cs#L171-L189' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_sticky_handlers_by_endpoint_with_fluent_interface' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Loading

0 comments on commit ff73eae

Please sign in to comment.