From 0bac0209390f6482668146e1561664b177d63584 Mon Sep 17 00:00:00 2001 From: Gerhard Gruber Date: Thu, 24 Jul 2025 11:01:59 +0200 Subject: [PATCH] Example added to illustrate how to publish to multiple different queues. --- Motor.NET.sln | 15 ++++ .../Model/InputMessage.cs | 7 ++ .../Model/LeftMessage.cs | 9 +++ .../Model/OutputMessage.cs | 7 ++ .../Model/RightMessage.cs | 8 +++ .../NoOutputService.cs | 68 ++++++++++++++++++ .../Program.cs | 72 +++++++++++++++++++ .../Properties/launchSettings.json | 27 +++++++ .../PublishToMultipleQueuesRabbitMQ.csproj | 25 +++++++ .../README.txt | 5 ++ .../appsettings.Production.json | 17 +++++ .../appsettings.json | 51 +++++++++++++ 12 files changed, 311 insertions(+) create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/Model/InputMessage.cs create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/Model/LeftMessage.cs create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/Model/OutputMessage.cs create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/Model/RightMessage.cs create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/NoOutputService.cs create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/Program.cs create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/Properties/launchSettings.json create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/PublishToMultipleQueuesRabbitMQ.csproj create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/README.txt create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/appsettings.Production.json create mode 100644 examples/PublishToMultipleQueuesRabbitMQ/appsettings.json diff --git a/Motor.NET.sln b/Motor.NET.sln index 51fa94c9f..64c0ad8c1 100644 --- a/Motor.NET.sln +++ b/Motor.NET.sln @@ -136,6 +136,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumeWithRabbitMQAndDeadL EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Motor.Extensions.Diagnostics.Telemetry_IntegrationTest", "test\Motor.Extensions.Diagnostics.Telemetry_IntegrationTest\Motor.Extensions.Diagnostics.Telemetry_IntegrationTest.csproj", "{976B6FB8-CEB0-4544-A22F-3CF78348E43B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublishToMultipleQueuesRabbitMQ", "examples\PublishToMultipleQueuesRabbitMQ\PublishToMultipleQueuesRabbitMQ.csproj", "{D2028002-1122-4F4B-9F38-E328FE4B8501}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -854,6 +856,18 @@ Global {976B6FB8-CEB0-4544-A22F-3CF78348E43B}.Release|x64.Build.0 = Release|Any CPU {976B6FB8-CEB0-4544-A22F-3CF78348E43B}.Release|x86.ActiveCfg = Release|Any CPU {976B6FB8-CEB0-4544-A22F-3CF78348E43B}.Release|x86.Build.0 = Release|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Debug|x64.ActiveCfg = Debug|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Debug|x64.Build.0 = Debug|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Debug|x86.ActiveCfg = Debug|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Debug|x86.Build.0 = Debug|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Release|Any CPU.Build.0 = Release|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Release|x64.ActiveCfg = Release|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Release|x64.Build.0 = Release|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Release|x86.ActiveCfg = Release|Any CPU + {D2028002-1122-4F4B-9F38-E328FE4B8501}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -918,6 +932,7 @@ Global {0896B5BC-CA32-40E6-A7CF-E9EF288C0C81} = {749B1421-3177-4C7A-A66B-541BD4E925B0} {142BFD82-3C48-4E9A-9F9C-BF7401E057B4} = {3DC7D216-6908-4759-B86F-759FDAE393D9} {976B6FB8-CEB0-4544-A22F-3CF78348E43B} = {ADD2EBBA-A839-4E4A-9253-CDE29A372F07} + {D2028002-1122-4F4B-9F38-E328FE4B8501} = {3DC7D216-6908-4759-B86F-759FDAE393D9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5E91C34C-3AEC-4084-BA02-753C9236AA34} diff --git a/examples/PublishToMultipleQueuesRabbitMQ/Model/InputMessage.cs b/examples/PublishToMultipleQueuesRabbitMQ/Model/InputMessage.cs new file mode 100644 index 000000000..64855598b --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/Model/InputMessage.cs @@ -0,0 +1,7 @@ +namespace PublishToMultipleQueuesRabbitMQ.Model; + +public record InputMessage +{ + public string FancyText { get; set; } = "FooBar"; + public int FancyNumber { get; set; } = 42; +} diff --git a/examples/PublishToMultipleQueuesRabbitMQ/Model/LeftMessage.cs b/examples/PublishToMultipleQueuesRabbitMQ/Model/LeftMessage.cs new file mode 100644 index 000000000..2efca15e1 --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/Model/LeftMessage.cs @@ -0,0 +1,9 @@ +namespace PublishToMultipleQueuesRabbitMQ.Model; + +// Since we send the same data to the queue, we can use a baseclass with the actual +// values, but still need to define an individual type for the queue. +public record LeftMessage : OutputMessage +{ + // Or add additional data here. + public string Left { get; set; } = "Left"; +} diff --git a/examples/PublishToMultipleQueuesRabbitMQ/Model/OutputMessage.cs b/examples/PublishToMultipleQueuesRabbitMQ/Model/OutputMessage.cs new file mode 100644 index 000000000..b73f6bd6b --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/Model/OutputMessage.cs @@ -0,0 +1,7 @@ +namespace PublishToMultipleQueuesRabbitMQ.Model; + +public record OutputMessage +{ + public string NotSoFancyText { get; set; } + public int NotSoFancyNumber { get; set; } +} diff --git a/examples/PublishToMultipleQueuesRabbitMQ/Model/RightMessage.cs b/examples/PublishToMultipleQueuesRabbitMQ/Model/RightMessage.cs new file mode 100644 index 000000000..58e70f869 --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/Model/RightMessage.cs @@ -0,0 +1,8 @@ +namespace PublishToMultipleQueuesRabbitMQ.Model; + +// Since we send the same data to the queue, we can use a baseclass with the actual +// values, but still need to define an individual type for the queue. +public record RightMessage : OutputMessage +{ + // Or add additional data here. +} diff --git a/examples/PublishToMultipleQueuesRabbitMQ/NoOutputService.cs b/examples/PublishToMultipleQueuesRabbitMQ/NoOutputService.cs new file mode 100644 index 000000000..dfba3d0f0 --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/NoOutputService.cs @@ -0,0 +1,68 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Motor.Extensions.Hosting.Abstractions; +using Motor.Extensions.Hosting.CloudEvents; +using PublishToMultipleQueuesRabbitMQ.Model; + +namespace PublishToMultipleQueuesRabbitMQ; + +public class NoOutputService( + ITypedMessagePublisher leftPublisher, + ITypedMessagePublisher rightPublisher) + : INoOutputService +{ + // Handle incoming messages + private static int _messageCount = 0; + + public Task HandleMessageAsync(MotorCloudEvent inputEvent, CancellationToken token = default) + { + // Get the input message from the cloud event + var input = inputEvent.TypedData; + + // Sometimes we dont have anything to publish. + if (string.IsNullOrEmpty(input.FancyText)) + { + // Might need to be logged, or published to some additional queue, or can simply be ignored. + return Task.FromResult(ProcessedMessageStatus.Success); + } + + _messageCount++; + + // In all other cases we publish to the queue dictated by our business logic. + if (_messageCount % 2 == 0) + { + var left = CreateLeftMessage(input); + leftPublisher.PublishMessageAsync(inputEvent.CreateNew(left), token); + } + else + { + var right = CreateRightMessage(input); + rightPublisher.PublishMessageAsync(inputEvent.CreateNew(right), token); + } + + return Task.FromResult(ProcessedMessageStatus.Success); + } + + private static LeftMessage CreateLeftMessage(InputMessage input) + { + var output = new LeftMessage + { + NotSoFancyText = input.FancyText.Reverse().ToString(), + NotSoFancyNumber = input.FancyNumber * -1, + }; + return output; + } + + private static RightMessage CreateRightMessage(InputMessage input) + { + var output = new RightMessage + { + NotSoFancyText = input.FancyText + " " + DateTime.Now, + NotSoFancyNumber = input.FancyNumber * 3, + }; + + return output; + } +} diff --git a/examples/PublishToMultipleQueuesRabbitMQ/Program.cs b/examples/PublishToMultipleQueuesRabbitMQ/Program.cs new file mode 100644 index 000000000..a7b4bcb21 --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/Program.cs @@ -0,0 +1,72 @@ +using PublishToMultipleQueuesRabbitMQ.Model; +using PublishToMultipleQueuesRabbitMQ; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Motor.Extensions.ContentEncoding.Gzip; +using Motor.Extensions.Conversion.SystemJson; +using Motor.Extensions.Hosting.Abstractions; +using Motor.Extensions.Hosting.Consumer; +using Motor.Extensions.Hosting.Publisher; +using Motor.Extensions.Hosting.RabbitMQ; +using Motor.Extensions.Utilities; + +await MotorHost.CreateDefaultBuilder() + // Configure the types of the input message + .ConfigureNoOutputService() + .ConfigureServices((_, services) => + { + // Add a handler for the input message. this handler is a INoOutputService + // because we would have to specify the output type here, which we can't because + // the handler wants to send to different queues, depending on the incoming + // message data and its associated business logic. + // This handler is called for every new incoming message. + services.AddTransient, NoOutputService>(); + }) + // Add the incoming communication modules. + .ConfigureConsumer((_, builder) => + { + // In this case the messages are received from RabbitMQ + builder.AddRabbitMQ(); + // The encoding of the incoming message, such that the handler is able to deserialize the message + builder.AddSystemJson(); + // (Optional) Enable support for incoming messages that are gzip compressed. Uncompressed messages will still + // work to make the migration to compression backwards-compatible. + builder.AddGzipDecompression(); + }) + // Now add the different queues. For each individual queue + // we have to add here individual sections. Because of the template argument, each + // section needs to have its own message type. You can currently not send the same + // message type to different queues. Note that these dont have to be separate objects though. + // So if you want to send the same message to multiple queues, you can define your data in a + // base class, and then create an empty derived type, which is to be used here in the setup (as + // shown in this example). + + // Add one publishing queue. + .ConfigurePublisher((_, builder) => + { + // In this case the messages are sent to one RabbitMQ queue. + // We could still use the default name for any of the config sections, but + // using a descriptive name is better, especially because we still have + // the other queue, or as many as needed, with different settings. + // Obviously only one queue can have the default name anyway. + builder.AddRabbitMQ("LeftQueue"); + + // The encoding of the outgoing message, such that the handler is able to serialize the message + builder.AddSystemJson(); + + // (Optional) Compress the serialized data of the outgoing message with gzip. + builder.AddGzipCompression(); + }) + // Add another publishing queue. + .ConfigurePublisher((_, builder) => + { + // In this case the messages are sent to the other RabbitMQ queue. + builder.AddRabbitMQ("RightQueue"); + + // The encoding of the outgoing message, such that the handler is able to serialize the message + builder.AddSystemJson(); + + // (Optional) Compress the serialized data of the outgoing message with gzip. + builder.AddGzipCompression(); + }) + .RunConsoleAsync(); diff --git a/examples/PublishToMultipleQueuesRabbitMQ/Properties/launchSettings.json b/examples/PublishToMultipleQueuesRabbitMQ/Properties/launchSettings.json new file mode 100644 index 000000000..05eee2cd1 --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:58904/", + "sslPort": 44350 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "ConsumeAndPublishWithRabbitMQ": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/examples/PublishToMultipleQueuesRabbitMQ/PublishToMultipleQueuesRabbitMQ.csproj b/examples/PublishToMultipleQueuesRabbitMQ/PublishToMultipleQueuesRabbitMQ.csproj new file mode 100644 index 000000000..ed0d7b8dd --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/PublishToMultipleQueuesRabbitMQ.csproj @@ -0,0 +1,25 @@ + + + + Exe + net8.0;net9.0 + Motor.NET + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/examples/PublishToMultipleQueuesRabbitMQ/README.txt b/examples/PublishToMultipleQueuesRabbitMQ/README.txt new file mode 100644 index 000000000..e39a5e7db --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/README.txt @@ -0,0 +1,5 @@ +This example shows how to use MotorNET to publish to multiple different queues. +The method shown here uses a RabbitMQ but of course the pattern can be applied to any of the +other publishers as well. Or it can be combined with other services as well. +So suppose you have a SingleOutputService which always publishes to a certain queue, +but in some cases you might want to send data to another queue. diff --git a/examples/PublishToMultipleQueuesRabbitMQ/appsettings.Production.json b/examples/PublishToMultipleQueuesRabbitMQ/appsettings.Production.json new file mode 100644 index 000000000..8a6469898 --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/appsettings.Production.json @@ -0,0 +1,17 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Information" + } + }, + "RabbitMQConsumer": { + "Queue": { + "Name": "ExampleProductionQueue" + } + }, + "RabbitMQPublisher": { + "PublishingTarget": { + "RoutingKey": "production" + } + } +} \ No newline at end of file diff --git a/examples/PublishToMultipleQueuesRabbitMQ/appsettings.json b/examples/PublishToMultipleQueuesRabbitMQ/appsettings.json new file mode 100644 index 000000000..12d2e51be --- /dev/null +++ b/examples/PublishToMultipleQueuesRabbitMQ/appsettings.json @@ -0,0 +1,51 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System": "Warning" + } + } + }, + "RabbitMQConsumer": { + "Host": "localhost", + "VirtualHost": "/", + "User": "guest", + "Password": "guest", + "Queue": { + "Name": "ExampleQueue", + "Bindings": [ + { + "Exchange": "amq.topic", + "RoutingKey": "input" + } + ] + }, + "PrefetchCount": 10 + }, + "LeftQueue": + { + "Host": "localhost", + "VirtualHost": "/", + "User": "guest", + "Password": "guest", + "PublishingTarget": + { + "Exchange": "amq.topic", + "RoutingKey": "output.left" + } + }, + "RightQueue": { + "Host": "localhost", + "VirtualHost": "/", + "User": "guest", + "Password": "guest", + "PublishingTarget": + { + "Exchange": "amq.topic", + "RoutingKey": "output.right" + } + } +} \ No newline at end of file