diff --git a/docs/guide/handlers/middleware.md b/docs/guide/handlers/middleware.md index 0ddceb3b..ee8979a6 100644 --- a/docs/guide/handlers/middleware.md +++ b/docs/guide/handlers/middleware.md @@ -531,7 +531,7 @@ public class WrapWithSimple : IHandlerPolicy } } ``` -snippet source | anchor +snippet source | anchor Then register your custom `IHandlerPolicy` with a Wolverine application like this: diff --git a/docs/guide/messaging/message-bus.md b/docs/guide/messaging/message-bus.md index 5a20e24d..7a745774 100644 --- a/docs/guide/messaging/message-bus.md +++ b/docs/guide/messaging/message-bus.md @@ -165,6 +165,29 @@ public class CreateItemCommandHandler snippet source | anchor +## Disabling Remote Request/Reply + +When you call `IMessageBus.InvokeAsync()` or `IMessageBus.InvokeAsync()`, depending on whether Wolverine has a local message +handler for the message type or has a configured subscription rule for the message type, Wolverine *might* be making a remote +call through external messaging transports to execute that message. It's a perfectly valid use case to do the remote +invocation, but if you don't want this to ever happen or catch a team by surprise when an operation fails, you can completely +disable all remote request/reply usage through `InvokeAsync()` by changing this setting: + + + +```cs +using var host = Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + // This will disallow Wolverine from making remote calls + // through IMessageBus.InvokeAsync() or InvokeAsync() + // Instead, Wolverine will throw an InvalidOperationException + opts.EnableRemoteInvocation = false; + }).StartAsync(); +``` +snippet source | anchor + + ## Sending or Publishing Messages [Publish/Subscribe](https://docs.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber) is a messaging pattern where the senders of messages do not need to specifically know what the specific subscribers are for a given message. In this case, some kind of middleware or infrastructure is responsible for either allowing subscribers to express interest in what messages they need to receive or apply routing rules to send the published messages to the right places. Wolverine's messaging support was largely built to support the publish/subscribe messaging pattern. diff --git a/src/Testing/CoreTests/BootstrappingSamples.cs b/src/Testing/CoreTests/BootstrappingSamples.cs index 4aeea0c8..68ed76f6 100644 --- a/src/Testing/CoreTests/BootstrappingSamples.cs +++ b/src/Testing/CoreTests/BootstrappingSamples.cs @@ -18,6 +18,22 @@ public static async Task AppWithHandlerPolicy() #endregion } + + public static async Task DisableRemoteInvocation() + { + #region sample_disabling_remote_invocation + + using var host = Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + // This will disallow Wolverine from making remote calls + // through IMessageBus.InvokeAsync() or InvokeAsync() + // Instead, Wolverine will throw an InvalidOperationException + opts.EnableRemoteInvocation = false; + }).StartAsync(); + + #endregion + } } #region sample_WrapWithSimple diff --git a/src/Testing/CoreTests/WolverineOptionsTests.cs b/src/Testing/CoreTests/WolverineOptionsTests.cs index 8e69b28f..abb06b43 100644 --- a/src/Testing/CoreTests/WolverineOptionsTests.cs +++ b/src/Testing/CoreTests/WolverineOptionsTests.cs @@ -205,6 +205,12 @@ public void find_named_transport() transport2.ShouldNotBeSameAs(transport1); } + [Fact] + public void enable_remote_invocation_is_true_by_default() + { + new WolverineOptions().EnableRemoteInvocation.ShouldBeTrue(); + } + public interface IFoo; public class Foo : IFoo; diff --git a/src/Wolverine/Runtime/Routing/MessageRoute.cs b/src/Wolverine/Runtime/Routing/MessageRoute.cs index 92d55780..fd5797ca 100644 --- a/src/Wolverine/Runtime/Routing/MessageRoute.cs +++ b/src/Wolverine/Runtime/Routing/MessageRoute.cs @@ -19,6 +19,7 @@ public class MessageRoute : IMessageRoute, IMessageInvoker ImHashMap>.Empty; private readonly IReplyTracker _replyTracker; + private readonly Endpoint _endpoint; public MessageRoute(Type messageType, Endpoint endpoint, IReplyTracker replies) { @@ -42,6 +43,8 @@ public MessageRoute(Type messageType, Endpoint endpoint, IReplyTracker replies) Rules.AddRange(RulesForMessageType(messageType)); MessageType = messageType; + + _endpoint = endpoint; } public Type MessageType { get; } @@ -120,6 +123,12 @@ public async Task InvokeAsync(object message, MessageBus bus, throw new ArgumentNullException(nameof(message)); } + if (!bus.Runtime.Options.EnableRemoteInvocation) + { + throw new InvalidOperationException( + $"Remote invocation is disabled in this application through the {nameof(WolverineOptions)}.{nameof(WolverineOptions.EnableRemoteInvocation)} value. Cannot invoke at requested endpoint {_endpoint.Uri}"); + } + bus.Runtime.RegisterMessageType(typeof(T)); timeout ??= 5.Seconds(); diff --git a/src/Wolverine/WolverineOptions.cs b/src/Wolverine/WolverineOptions.cs index 291e18b5..5f81621c 100644 --- a/src/Wolverine/WolverineOptions.cs +++ b/src/Wolverine/WolverineOptions.cs @@ -124,6 +124,13 @@ public WolverineOptions(string? assemblyName) internal LocalTransport LocalRouting => Transports.GetOrCreate(); internal bool LocalRoutingConventionDisabled { get; set; } + /// + /// Should remote usages of IMessageBus.InvokeAsync() or IMessageBus.InvokeAsync() + /// that ultimately use an external transport be enabled? Default is true, but you + /// may want to disable this to avoid surprise network round trips + /// + public bool EnableRemoteInvocation { get; set; } = true; + private void deriveServiceName() { if (GetType() == typeof(WolverineOptions))