diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index ee340e3d..c560f1f0 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-format": { - "version": "5.0.142902", + "version": "5.0.206801", "commands": [ "dotnet-format" ] diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 4068214d..d09e6c67 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -23,7 +23,7 @@ jobs: with: dotnet-version: 5.0.x - name: restore format - run: dotnet tool install dotnet-format --version 5.0.142902+8012f4ede1bb9a4e90ac01efd204b0f1ee428f10 --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json + run: dotnet tool restore --configfile NuGet.config - name: check format run: dotnet format --check ./Motor.NET.sln diff --git a/Motor.NET.sln b/Motor.NET.sln index e17d730d..311d8b80 100644 --- a/Motor.NET.sln +++ b/Motor.NET.sln @@ -88,10 +88,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{3D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumeAndPublishWithRabbitMQ", "examples\ConsumeAndPublishWithRabbitMQ\ConsumeAndPublishWithRabbitMQ.csproj", "{946D0F5D-EC69-49A7-A7A9-16B4A0648247}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metrics", "examples\Metrics\Metrics.csproj", "{66983F53-AA09-46B7-839C-F23BAE73433C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetricsExample", "examples\MetricsExample\MetricsExample.csproj", "{66983F53-AA09-46B7-839C-F23BAE73433C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumeAndPublishWithKafka", "examples\ConsumeAndPublishWithKafka\ConsumeAndPublishWithKafka.csproj", "{FA64170E-BF10-4670-84C6-1EF36E143E08}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumeAndMultiOutputPublisherWithRabbitMQ", "examples\ConsumeAndMultiOutputPublisherWithRabbitMQ\ConsumeAndMultiOutputPublisherWithRabbitMQ.csproj", "{F8386DEA-8905-4157-B949-CC151F3FB8D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetryExample", "examples\OpenTelemetryExample\OpenTelemetryExample.csproj", "{E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -558,6 +562,30 @@ Global {FA64170E-BF10-4670-84C6-1EF36E143E08}.Release|x64.Build.0 = Release|Any CPU {FA64170E-BF10-4670-84C6-1EF36E143E08}.Release|x86.ActiveCfg = Release|Any CPU {FA64170E-BF10-4670-84C6-1EF36E143E08}.Release|x86.Build.0 = Release|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Debug|x64.ActiveCfg = Debug|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Debug|x64.Build.0 = Debug|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Debug|x86.ActiveCfg = Debug|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Debug|x86.Build.0 = Debug|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Release|Any CPU.Build.0 = Release|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Release|x64.ActiveCfg = Release|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Release|x64.Build.0 = Release|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Release|x86.ActiveCfg = Release|Any CPU + {F8386DEA-8905-4157-B949-CC151F3FB8D2}.Release|x86.Build.0 = Release|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Debug|x64.ActiveCfg = Debug|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Debug|x64.Build.0 = Debug|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Debug|x86.ActiveCfg = Debug|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Debug|x86.Build.0 = Debug|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Release|Any CPU.Build.0 = Release|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Release|x64.ActiveCfg = Release|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Release|x64.Build.0 = Release|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Release|x86.ActiveCfg = Release|Any CPU + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -601,6 +629,8 @@ Global {946D0F5D-EC69-49A7-A7A9-16B4A0648247} = {3DC7D216-6908-4759-B86F-759FDAE393D9} {66983F53-AA09-46B7-839C-F23BAE73433C} = {3DC7D216-6908-4759-B86F-759FDAE393D9} {FA64170E-BF10-4670-84C6-1EF36E143E08} = {3DC7D216-6908-4759-B86F-759FDAE393D9} + {F8386DEA-8905-4157-B949-CC151F3FB8D2} = {3DC7D216-6908-4759-B86F-759FDAE393D9} + {E7F2E446-E2F5-4AD9-8328-42F1B5B4E245} = {3DC7D216-6908-4759-B86F-759FDAE393D9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5E91C34C-3AEC-4084-BA02-753C9236AA34} diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 00000000..47f01f5d --- /dev/null +++ b/NuGet.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Readme.md b/Readme.md index 877b4433..c4e48b08 100644 --- a/Readme.md +++ b/Readme.md @@ -16,7 +16,9 @@ You find working examples for different use-cases under the [examples](./example - [Consume and publish to RabbitMQ](./examples/ConsumeAndPublishWithRabbitMQ) - [Consume and publish to Kafka](./examples/ConsumeAndPublishWithKafka) -- [Create a service with metrics](./examples/Metrics) +- [Consume and publish multiple messages at once to RabbitMQ](./examples/ConsumeAndMultiOutputPublishWithRabbitMQ) +- [Create a service with metrics](./examples/MetricsExample) +- [Create a service with custom traces](./examples/OpenTelemetryExample) ## Support Matrix diff --git a/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/ConsumeAndMultiOutputPublisherWithRabbitMQ.csproj b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/ConsumeAndMultiOutputPublisherWithRabbitMQ.csproj new file mode 100644 index 00000000..7640af21 --- /dev/null +++ b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/ConsumeAndMultiOutputPublisherWithRabbitMQ.csproj @@ -0,0 +1,24 @@ + + + + Exe + net5.0 + Motor.NET + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Model/InputMessage.cs b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Model/InputMessage.cs new file mode 100644 index 00000000..7e1ff72b --- /dev/null +++ b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Model/InputMessage.cs @@ -0,0 +1,8 @@ +namespace ConsumeAndMultiOutputPublisherWithRabbitMQ.Model +{ + public record InputMessage + { + public string FancyText { get; set; } = "FooBar"; + public int FancyNumber { get; set; } = 42; + } +} diff --git a/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Model/OutputMessage.cs b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Model/OutputMessage.cs new file mode 100644 index 00000000..5621f276 --- /dev/null +++ b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Model/OutputMessage.cs @@ -0,0 +1,8 @@ +namespace ConsumeAndMultiOutputPublisherWithRabbitMQ.Model +{ + public record OutputMessage + { + public string NotSoFancyText { get; set; } + public int NotSoFancyNumber { get; set; } + } +} diff --git a/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/MultiOutputService.cs b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/MultiOutputService.cs new file mode 100644 index 00000000..49f84a3b --- /dev/null +++ b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/MultiOutputService.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ConsumeAndMultiOutputPublisherWithRabbitMQ.Model; +using Motor.Extensions.Hosting.Abstractions; + +namespace ConsumeAndMultiOutputPublisherWithRabbitMQ +{ + public class MultiOutputService : IMultiOutputService + { + // Handle incoming messages + public Task>> ConvertMessageAsync( + MotorCloudEvent inputEvent, + CancellationToken token = default) + { + // Get the input message from the cloud event + var input = inputEvent.TypedData; + + // Do your magic here ..... + var output = MagicFunc(input); + + // Create a new cloud event from your output message which is automatically published and return a new task. + var outputEvent = output.Select(singleEvent => inputEvent.CreateNew(singleEvent)); + return Task.FromResult(outputEvent); + } + + private static IEnumerable MagicFunc(InputMessage input) + { + if (string.IsNullOrEmpty(input.FancyText)) + { + // Reject message in RabbitMQ queue (Any ArgumentException can be used to reject to messages.). + throw new ArgumentNullException("FancyText is empty"); + } + + return new List + { + new() + { + NotSoFancyText = input.FancyText.Reverse().ToString(), + NotSoFancyNumber = input.FancyNumber * -1, + }, + new() + { + NotSoFancyText = input.FancyText, + NotSoFancyNumber = input.FancyNumber * -2, + }, + }; + } + } +} diff --git a/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Program.cs b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Program.cs new file mode 100644 index 00000000..8bfecc67 --- /dev/null +++ b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Program.cs @@ -0,0 +1,37 @@ +using ConsumeAndMultiOutputPublisherWithRabbitMQ; +using ConsumeAndMultiOutputPublisherWithRabbitMQ.Model; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +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 and output messages + .ConfigureMultiOutputService() + .ConfigureServices((_, services) => + { + // Add a handler for the input message which returns an output message + // This handler is called for every new incoming message + services.AddTransient, MultiOutputService>(); + }) + // Add the incomming communication module. + .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(); + }) + // Add the outgoing communication module. + .ConfigurePublisher((_, builder) => + { + // In this case the messages are send to RabbitMQ + builder.AddRabbitMQ(); + // The encoding of the outgoing message, such that the handler is able to serialize the message + builder.AddSystemJson(); + }) + .RunConsoleAsync(); diff --git a/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Properties/launchSettings.json b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/Properties/launchSettings.json new file mode 100644 index 00000000..d16586d2 --- /dev/null +++ b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/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/ConsumeAndMultiOutputPublisherWithRabbitMQ/appsettings.Production.json b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/appsettings.Production.json new file mode 100644 index 00000000..8a646989 --- /dev/null +++ b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/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/ConsumeAndMultiOutputPublisherWithRabbitMQ/appsettings.json b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/appsettings.json new file mode 100644 index 00000000..e773aac7 --- /dev/null +++ b/examples/ConsumeAndMultiOutputPublisherWithRabbitMQ/appsettings.json @@ -0,0 +1,38 @@ +{ + "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 + }, + "RabbitMQPublisher": { + "Host": "localhost", + "VirtualHost": "/", + "User": "guest", + "Password": "guest", + "PublishingTarget": { + "Exchange": "amq.topic", + "RoutingKey": "ouput" + } + } +} \ No newline at end of file diff --git a/examples/Metrics/DifferentNamespace/ServiceInDifferentNamespace.cs b/examples/MetricsExample/DifferentNamespace/ServiceInDifferentNamespace.cs similarity index 81% rename from examples/Metrics/DifferentNamespace/ServiceInDifferentNamespace.cs rename to examples/MetricsExample/DifferentNamespace/ServiceInDifferentNamespace.cs index b6e4564b..335f13dc 100644 --- a/examples/Metrics/DifferentNamespace/ServiceInDifferentNamespace.cs +++ b/examples/MetricsExample/DifferentNamespace/ServiceInDifferentNamespace.cs @@ -1,7 +1,7 @@ using Motor.Extensions.Diagnostics.Metrics.Abstractions; using Prometheus.Client.Abstractions; -namespace Metrics.DifferentNamespace +namespace MetricsExample.DifferentNamespace { public interface IServiceInDifferentNamespace { @@ -14,7 +14,7 @@ public class ServiceInDifferentNamespace : IServiceInDifferentNamespace public ServiceInDifferentNamespace(IMetricsFactory? metricsFactory) { - // Resulting label in Prometheus: metrics_differentnamespace_counter_in_different_namespace + // Resulting label in Prometheus: metricsexample_differentnamespace_counter_in_different_namespace _counter = metricsFactory?.CreateCounter("counter_in_different_namespace", "This counts something else."); } diff --git a/examples/Metrics/Metrics.csproj b/examples/MetricsExample/MetricsExample.csproj similarity index 100% rename from examples/Metrics/Metrics.csproj rename to examples/MetricsExample/MetricsExample.csproj diff --git a/examples/Metrics/Model/InputMessage.cs b/examples/MetricsExample/Model/InputMessage.cs similarity index 83% rename from examples/Metrics/Model/InputMessage.cs rename to examples/MetricsExample/Model/InputMessage.cs index 4dd178f9..5fc3680d 100644 --- a/examples/Metrics/Model/InputMessage.cs +++ b/examples/MetricsExample/Model/InputMessage.cs @@ -1,4 +1,4 @@ -namespace Metrics.Model +namespace MetricsExample.Model { public record InputMessage { diff --git a/examples/Metrics/Program.cs b/examples/MetricsExample/Program.cs similarity index 92% rename from examples/Metrics/Program.cs rename to examples/MetricsExample/Program.cs index caa32ed5..8091ce3f 100644 --- a/examples/Metrics/Program.cs +++ b/examples/MetricsExample/Program.cs @@ -1,6 +1,6 @@ -using Metrics; -using Metrics.DifferentNamespace; -using Metrics.Model; +using MetricsExample; +using MetricsExample.DifferentNamespace; +using MetricsExample.Model; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Motor.Extensions.Conversion.SystemJson; diff --git a/examples/Metrics/Properties/launchSettings.json b/examples/MetricsExample/Properties/launchSettings.json similarity index 100% rename from examples/Metrics/Properties/launchSettings.json rename to examples/MetricsExample/Properties/launchSettings.json diff --git a/examples/Metrics/ServiceWithMetrics.cs b/examples/MetricsExample/ServiceWithMetrics.cs similarity index 87% rename from examples/Metrics/ServiceWithMetrics.cs rename to examples/MetricsExample/ServiceWithMetrics.cs index a1092126..3c9cbf67 100644 --- a/examples/Metrics/ServiceWithMetrics.cs +++ b/examples/MetricsExample/ServiceWithMetrics.cs @@ -1,12 +1,12 @@ using System.Threading; using System.Threading.Tasks; -using Metrics.DifferentNamespace; -using Metrics.Model; +using MetricsExample.DifferentNamespace; +using MetricsExample.Model; using Motor.Extensions.Diagnostics.Metrics.Abstractions; using Motor.Extensions.Hosting.Abstractions; using Prometheus.Client.Abstractions; -namespace Metrics +namespace MetricsExample { public class ServiceWithMetrics : INoOutputService { @@ -19,11 +19,11 @@ public ServiceWithMetrics(IMetricsFactory metricFactory, { _serviceInDifferentNamespace = serviceInDifferentNamespace; - // Resulting label in Prometheus: metrics_emtpy_string_total + // Resulting label in Prometheus: metricsexample_emtpy_string_total _counter = metricFactory?.CreateCounter("empty_string_total", "Counts the total number of recieved empty strings."); - // Resulting label in Prometheus: metrics_fancy_number + // Resulting label in Prometheus: metricsexample_fancy_number _summary = metricFactory?.CreateSummary("fancy_number", "Shows the distribution of fancy numbers."); } diff --git a/examples/Metrics/appsettings.Production.json b/examples/MetricsExample/appsettings.Production.json similarity index 100% rename from examples/Metrics/appsettings.Production.json rename to examples/MetricsExample/appsettings.Production.json diff --git a/examples/Metrics/appsettings.json b/examples/MetricsExample/appsettings.json similarity index 100% rename from examples/Metrics/appsettings.json rename to examples/MetricsExample/appsettings.json diff --git a/examples/OpenTelemetryExample/Model/InputMessage.cs b/examples/OpenTelemetryExample/Model/InputMessage.cs new file mode 100644 index 00000000..67189c66 --- /dev/null +++ b/examples/OpenTelemetryExample/Model/InputMessage.cs @@ -0,0 +1,8 @@ +namespace OpenTelemetryExample.Model +{ + public record InputMessage + { + public string FancyText { get; set; } = "FooBar"; + public int FancyNumber { get; set; } = 42; + } +} diff --git a/examples/OpenTelemetryExample/Model/OutputMessage.cs b/examples/OpenTelemetryExample/Model/OutputMessage.cs new file mode 100644 index 00000000..f3101f65 --- /dev/null +++ b/examples/OpenTelemetryExample/Model/OutputMessage.cs @@ -0,0 +1,8 @@ +namespace OpenTelemetryExample.Model +{ + public record OutputMessage + { + public string NotSoFancyText { get; set; } + public int NotSoFancyNumber { get; set; } + } +} diff --git a/examples/OpenTelemetryExample/OpenTelemetryExample.csproj b/examples/OpenTelemetryExample/OpenTelemetryExample.csproj new file mode 100644 index 00000000..36f1408e --- /dev/null +++ b/examples/OpenTelemetryExample/OpenTelemetryExample.csproj @@ -0,0 +1,27 @@ + + + + Exe + net5.0 + Motor.NET + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/examples/OpenTelemetryExample/Program.cs b/examples/OpenTelemetryExample/Program.cs new file mode 100644 index 00000000..9d4ff3e8 --- /dev/null +++ b/examples/OpenTelemetryExample/Program.cs @@ -0,0 +1,37 @@ +using OpenTelemetryExample; +using OpenTelemetryExample.Model; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +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 and output messages + .ConfigureSingleOutputService() + .ConfigureServices((_, services) => + { + // Add a handler for the input message which returns an output message + // This handler is called for every new incoming message + services.AddTransient, SingleOutputService>(); + }) + // Add the incomming communication module. + .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(); + }) + // Add the outgoing communication module. + .ConfigurePublisher((_, builder) => + { + // In this case the messages are send to RabbitMQ + builder.AddRabbitMQ(); + // The encoding of the outgoing message, such that the handler is able to serialize the message + builder.AddSystemJson(); + }) + .RunConsoleAsync(); diff --git a/examples/OpenTelemetryExample/Properties/launchSettings.json b/examples/OpenTelemetryExample/Properties/launchSettings.json new file mode 100644 index 00000000..d16586d2 --- /dev/null +++ b/examples/OpenTelemetryExample/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/OpenTelemetryExample/SingleOutputService.cs b/examples/OpenTelemetryExample/SingleOutputService.cs new file mode 100644 index 00000000..3fdfb33f --- /dev/null +++ b/examples/OpenTelemetryExample/SingleOutputService.cs @@ -0,0 +1,62 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CloudNative.CloudEvents.Extensions; +using Motor.Extensions.Diagnostics.Tracing; +using OpenTelemetryExample.Model; +using Motor.Extensions.Hosting.Abstractions; + +namespace OpenTelemetryExample +{ + public class SingleOutputService : ISingleOutputService + { + private static readonly ActivitySource ActivitySource = new(nameof(OpenTelemetryExample)); + + // Handle incoming messages + public Task> ConvertMessageAsync( + MotorCloudEvent inputEvent, + CancellationToken token = default) + { + // Get the input message from the cloud event + var input = inputEvent.TypedData; + + if (string.IsNullOrEmpty(input.FancyText)) + { + // Reject message in RabbitMQ queue (Any ArgumentException can be used to reject to messages.). + throw new ArgumentNullException("FancyText is empty"); + } + + // Extract ActivityContext from the incoming CloudEvent + var parentContext = inputEvent.Extension()?.GetActivityContext() ?? default; + + // Create new Activity with extracted ActivityContext as parent + using var activity = + ActivitySource.StartActivity(nameof(MagicFunc), ActivityKind.Consumer, parentContext); + activity?.SetTag("fancyText", input.FancyText); + + // Add Trace for MagicFunc with a tag + OutputMessage output; + using (activity?.Start()) + { + // Do your magic here ..... + output = MagicFunc(input); + } + + // Create a new cloud event from your output message which is automatically published and return a new task. + var outputEvent = inputEvent.CreateNew(output); + return Task.FromResult(outputEvent); + } + + private static OutputMessage MagicFunc(InputMessage input) + { + var output = new OutputMessage + { + NotSoFancyText = input.FancyText.Reverse().ToString(), + NotSoFancyNumber = input.FancyNumber * -1, + }; + return output; + } + } +} diff --git a/examples/OpenTelemetryExample/appsettings.Development.json b/examples/OpenTelemetryExample/appsettings.Development.json new file mode 100644 index 00000000..4c4df39c --- /dev/null +++ b/examples/OpenTelemetryExample/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Information" + } + }, + "JaegerExporter": { + "AgentHost": "localhost" + } +} \ No newline at end of file diff --git a/examples/OpenTelemetryExample/appsettings.Production.json b/examples/OpenTelemetryExample/appsettings.Production.json new file mode 100644 index 00000000..050047f7 --- /dev/null +++ b/examples/OpenTelemetryExample/appsettings.Production.json @@ -0,0 +1,20 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Information" + } + }, + "OpenTelemetry": { + "SamplingProbability": 0.001 + }, + "RabbitMQConsumer": { + "Queue": { + "Name": "ExampleProductionQueue" + } + }, + "RabbitMQPublisher": { + "PublishingTarget": { + "RoutingKey": "production" + } + } +} \ No newline at end of file diff --git a/examples/OpenTelemetryExample/appsettings.json b/examples/OpenTelemetryExample/appsettings.json new file mode 100644 index 00000000..64f53acd --- /dev/null +++ b/examples/OpenTelemetryExample/appsettings.json @@ -0,0 +1,45 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System": "Warning" + } + } + }, + "JaegerExporter": { + "AgentHost": "agent-host.localhost" + }, + "OpenTelemetry": { + "SamplingProbability": 1, + "Sources": ["OpenTelemetryExample", "MotorNet.OpenTelemetry"] + }, + "RabbitMQConsumer": { + "Host": "localhost", + "VirtualHost": "/", + "User": "guest", + "Password": "guest", + "Queue": { + "Name": "ExampleQueue", + "Bindings": [ + { + "Exchange": "amq.topic", + "RoutingKey": "input" + } + ] + }, + "PrefetchCount": 10 + }, + "RabbitMQPublisher": { + "Host": "localhost", + "VirtualHost": "/", + "User": "guest", + "Password": "guest", + "PublishingTarget": { + "Exchange": "amq.topic", + "RoutingKey": "ouput" + } + } +} \ No newline at end of file