From cef81c2cd34ae98808864495e437788412e18f8a Mon Sep 17 00:00:00 2001 From: Aymen TROUDI Date: Sat, 7 Dec 2024 00:55:47 +0100 Subject: [PATCH] Add Example05 --- Directory.Packages.props | 1 + README.md | 4 +- ServiceBusDemo.sln | 7 +++ src/Example01/DependencyInjection.cs | 6 +-- src/Example02/DependencyInjection.cs | 6 +-- src/Example03/DependencyInjection.cs | 6 +-- src/Example04/DependencyInjection.cs | 4 +- src/Example05/Configuration/Settings.cs | 11 +++++ .../Configuration/SettingsValidator.cs | 26 ++++++++++ src/Example05/Consumers/MessageConsumer.cs | 25 ++++++++++ src/Example05/Contracts/Message.cs | 13 +++++ src/Example05/DependencyInjection.cs | 47 +++++++++++++++++++ src/Example05/Example05.csproj | 13 +++++ src/Example05/Extensions/LoggingExtensions.cs | 10 ++++ src/Example05/Producers/MessageProducer.cs | 35 ++++++++++++++ src/Example05/Program.cs | 6 +++ src/Example05/Properties/launchSettings.json | 12 +++++ src/Example05/appsettings.Development.json | 8 ++++ src/Example05/appsettings.json | 19 ++++++++ 19 files changed, 247 insertions(+), 12 deletions(-) create mode 100644 src/Example05/Configuration/Settings.cs create mode 100644 src/Example05/Configuration/SettingsValidator.cs create mode 100644 src/Example05/Consumers/MessageConsumer.cs create mode 100644 src/Example05/Contracts/Message.cs create mode 100644 src/Example05/DependencyInjection.cs create mode 100644 src/Example05/Example05.csproj create mode 100644 src/Example05/Extensions/LoggingExtensions.cs create mode 100644 src/Example05/Producers/MessageProducer.cs create mode 100644 src/Example05/Program.cs create mode 100644 src/Example05/Properties/launchSettings.json create mode 100644 src/Example05/appsettings.Development.json create mode 100644 src/Example05/appsettings.json diff --git a/Directory.Packages.props b/Directory.Packages.props index 9240c74..c1680d5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,5 +8,6 @@ + \ No newline at end of file diff --git a/README.md b/README.md index 03b1d4d..7d74f5a 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,7 @@ Playing with service bus > > :four: `Example04` : use worker template with [MassTransit](https://masstransit-project.com) > +> :five: `Example05` : use worker template with [Wolverine](https://wolverinefx.net) +> -**`Tools`** : net 8.0, servicebus, masstransit +**`Tools`** : net 8.0, servicebus, masstransit, wolverine diff --git a/ServiceBusDemo.sln b/ServiceBusDemo.sln index 6d1eabf..0d381c8 100644 --- a/ServiceBusDemo.sln +++ b/ServiceBusDemo.sln @@ -24,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example02", "src\Example02\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example04", "src\Example04\Example04.csproj", "{EFA5CB6E-D00C-450F-98BC-0A94B59A7AD4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example05", "src\Example05\Example05.csproj", "{18585800-512B-4E0B-88A5-C9556465F17E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,11 +51,16 @@ Global {EFA5CB6E-D00C-450F-98BC-0A94B59A7AD4}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFA5CB6E-D00C-450F-98BC-0A94B59A7AD4}.Release|Any CPU.ActiveCfg = Release|Any CPU {EFA5CB6E-D00C-450F-98BC-0A94B59A7AD4}.Release|Any CPU.Build.0 = Release|Any CPU + {18585800-512B-4E0B-88A5-C9556465F17E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18585800-512B-4E0B-88A5-C9556465F17E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18585800-512B-4E0B-88A5-C9556465F17E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18585800-512B-4E0B-88A5-C9556465F17E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {1F1CAFDA-2CE5-4248-B437-512552BFF221} = {675A9474-CB75-4BB9-96ED-7A4F6DBF6DD8} {35DB9CC1-DC4E-4891-A1C1-12A2BBA47E08} = {675A9474-CB75-4BB9-96ED-7A4F6DBF6DD8} {C5AA6BD9-2F7B-4F7C-AE2E-3BAE12B34E29} = {675A9474-CB75-4BB9-96ED-7A4F6DBF6DD8} {EFA5CB6E-D00C-450F-98BC-0A94B59A7AD4} = {675A9474-CB75-4BB9-96ED-7A4F6DBF6DD8} + {18585800-512B-4E0B-88A5-C9556465F17E} = {675A9474-CB75-4BB9-96ED-7A4F6DBF6DD8} EndGlobalSection EndGlobal diff --git a/src/Example01/DependencyInjection.cs b/src/Example01/DependencyInjection.cs index 508cd97..7179e1e 100644 --- a/src/Example01/DependencyInjection.cs +++ b/src/Example01/DependencyInjection.cs @@ -22,15 +22,15 @@ private static void AddSettings(this HostApplicationBuilder builder) private static void AddServiceBus(this HostApplicationBuilder builder) { - builder.Services.AddHostedService(); - builder.Services.AddHostedService(); - builder.Services.AddSingleton(sp => { var settings = sp.GetRequiredService>().Value; var client = GetServiceBusClient(settings); return client; }); + + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); } private static ServiceBusClient GetServiceBusClient(Settings settings) diff --git a/src/Example02/DependencyInjection.cs b/src/Example02/DependencyInjection.cs index 6c923f3..6a171e1 100644 --- a/src/Example02/DependencyInjection.cs +++ b/src/Example02/DependencyInjection.cs @@ -22,9 +22,6 @@ private static void AddSettings(this HostApplicationBuilder builder) private static void AddServiceBus(this HostApplicationBuilder builder) { - builder.Services.AddHostedService(); - builder.Services.AddHostedService(); - builder.Services.AddSingleton(sp => { var settings = sp.GetRequiredService>().Value; @@ -40,6 +37,9 @@ private static void AddServiceBus(this HostApplicationBuilder builder) var sender = GetServiceBusSender(client, settings); return sender; }); + + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); } private static ServiceBusClient GetServiceBusClient(Settings settings) diff --git a/src/Example03/DependencyInjection.cs b/src/Example03/DependencyInjection.cs index caeb97e..97f60d3 100644 --- a/src/Example03/DependencyInjection.cs +++ b/src/Example03/DependencyInjection.cs @@ -22,9 +22,6 @@ private static void AddSettings(this HostApplicationBuilder builder) private static void AddServiceBus(this HostApplicationBuilder builder) { - builder.Services.AddHostedService(); - builder.Services.AddHostedService(); - builder.Services.AddSingleton(sp => { var settings = sp.GetRequiredService>().Value; @@ -39,6 +36,9 @@ private static void AddServiceBus(this HostApplicationBuilder builder) var processor = GetServiceBusProcessor(client, settings); return processor; }); + + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); } private static ServiceBusClient GetServiceBusClient(Settings settings) diff --git a/src/Example04/DependencyInjection.cs b/src/Example04/DependencyInjection.cs index 4b2c1ba..38b0ce9 100644 --- a/src/Example04/DependencyInjection.cs +++ b/src/Example04/DependencyInjection.cs @@ -25,8 +25,6 @@ private static void AddServiceBus(this HostApplicationBuilder builder) { var hostSettings = builder.Configuration.GetHostSettings(); - builder.Services.AddHostedService(); - builder.Services.AddMassTransit(configure => { configure.SetKebabCaseEndpointNameFormatter(); @@ -37,6 +35,8 @@ private static void AddServiceBus(this HostApplicationBuilder builder) cfg.ConfigureEndpoints(context); }); }); + + builder.Services.AddHostedService(); } private static HostSettings GetHostSettings(this ConfigurationManager configuration) diff --git a/src/Example05/Configuration/Settings.cs b/src/Example05/Configuration/Settings.cs new file mode 100644 index 0000000..f03036a --- /dev/null +++ b/src/Example05/Configuration/Settings.cs @@ -0,0 +1,11 @@ +namespace Example05.Configuration; + +public sealed record Settings +{ + public const string SectionName = "Settings"; + + public string QueueName { get; init; } = default!; + public string ConnectionString { get; init; } = default!; + public TimeSpan ConsumerDelay { get; init; } = TimeSpan.FromSeconds(1); + public TimeSpan ProducerDelay { get; init; } = TimeSpan.FromSeconds(1); +} \ No newline at end of file diff --git a/src/Example05/Configuration/SettingsValidator.cs b/src/Example05/Configuration/SettingsValidator.cs new file mode 100644 index 0000000..85667a8 --- /dev/null +++ b/src/Example05/Configuration/SettingsValidator.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Options; + +namespace Example05.Configuration; + +public sealed class SettingsValidator : IValidateOptions +{ + public ValidateOptionsResult Validate(string? name, Settings? settings) + { + if (settings is null) + { + return ValidateOptionsResult.Fail($"{nameof(Settings)} is required."); + } + + if (string.IsNullOrWhiteSpace(settings.QueueName)) + { + return ValidateOptionsResult.Fail($"{nameof(Settings.QueueName)} is required."); + } + + if (string.IsNullOrWhiteSpace(settings.ConnectionString)) + { + return ValidateOptionsResult.Fail($"{nameof(Settings.ConnectionString)} is required."); + } + + return ValidateOptionsResult.Success; + } +} \ No newline at end of file diff --git a/src/Example05/Consumers/MessageConsumer.cs b/src/Example05/Consumers/MessageConsumer.cs new file mode 100644 index 0000000..5e05a7e --- /dev/null +++ b/src/Example05/Consumers/MessageConsumer.cs @@ -0,0 +1,25 @@ +using Example05.Configuration; +using Example05.Contracts; +using Example05.Extensions; +using Microsoft.Extensions.Options; + +namespace Example05.Consumers; + +public sealed class MessageConsumer +{ + private readonly IOptions _options; + private readonly ILogger _logger; + + public MessageConsumer(IOptions options, ILogger logger) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Handle(Message message) + { + _logger.LogConsumedMessage(message.Id); + + await Task.Delay(_options.Value.ConsumerDelay); + } +} \ No newline at end of file diff --git a/src/Example05/Contracts/Message.cs b/src/Example05/Contracts/Message.cs new file mode 100644 index 0000000..dbb6362 --- /dev/null +++ b/src/Example05/Contracts/Message.cs @@ -0,0 +1,13 @@ +namespace Example05.Contracts; + +public sealed record Message +{ + public Guid Id { get; init; } + public string Text { get; init; } + + public Message() + { + Id = Guid.NewGuid(); + Text = $"Text for Id {Id}"; + } +} \ No newline at end of file diff --git a/src/Example05/DependencyInjection.cs b/src/Example05/DependencyInjection.cs new file mode 100644 index 0000000..70a8ee0 --- /dev/null +++ b/src/Example05/DependencyInjection.cs @@ -0,0 +1,47 @@ +using Example05.Configuration; +using Example05.Consumers; +using Example05.Contracts; +using Example05.Producers; +using Microsoft.Extensions.Options; +using Wolverine; +using Wolverine.AzureServiceBus; + +namespace Example05; + +public static class DependencyInjection +{ + public static void AddServices(this HostApplicationBuilder builder) + { + builder.AddSettings(); + builder.AddServiceBus(); + } + + private static void AddSettings(this HostApplicationBuilder builder) + { + builder.Services.Configure(builder.Configuration.GetSection(Settings.SectionName)); + builder.Services.AddSingleton, SettingsValidator>(); + } + + private static void AddServiceBus(this HostApplicationBuilder builder) + { + var settings = builder.Configuration.GetSettings(); + + builder.UseWolverine(options => + { + options.UseAzureServiceBus(settings.ConnectionString).AutoProvision(); + options.PublishMessage().ToAzureServiceBusQueue(settings.QueueName); + options.ListenToAzureServiceBusQueue(settings.QueueName); + }); + + builder.Services.AddSingleton(); + + builder.Services.AddHostedService(); + } + + private static Settings GetSettings(this ConfigurationManager configuration) + { + var settings = new Settings(); + configuration.GetSection(Settings.SectionName).Bind(settings); + return settings; + } +} \ No newline at end of file diff --git a/src/Example05/Example05.csproj b/src/Example05/Example05.csproj new file mode 100644 index 0000000..60a6d3b --- /dev/null +++ b/src/Example05/Example05.csproj @@ -0,0 +1,13 @@ + + + + ServiceBusDemo-Example05-UserSecrets + + + + + + + + + diff --git a/src/Example05/Extensions/LoggingExtensions.cs b/src/Example05/Extensions/LoggingExtensions.cs new file mode 100644 index 0000000..f69f70f --- /dev/null +++ b/src/Example05/Extensions/LoggingExtensions.cs @@ -0,0 +1,10 @@ +namespace Example05.Extensions; + +public static partial class LoggingExtensions +{ + [LoggerMessage(Level = LogLevel.Information, Message = "Message ({MessageId}) consumed.")] + public static partial void LogConsumedMessage(this ILogger logger, Guid messageId); + + [LoggerMessage(Level = LogLevel.Information, Message = "Message ({MessageId}) produced.")] + public static partial void LogProducedMessage(this ILogger logger, Guid messageId); +} \ No newline at end of file diff --git a/src/Example05/Producers/MessageProducer.cs b/src/Example05/Producers/MessageProducer.cs new file mode 100644 index 0000000..6176e46 --- /dev/null +++ b/src/Example05/Producers/MessageProducer.cs @@ -0,0 +1,35 @@ +using Example05.Configuration; +using Example05.Contracts; +using Example05.Extensions; +using Microsoft.Extensions.Options; +using Wolverine; + +namespace Example05.Producers; + +public sealed class MessageProducer : BackgroundService +{ + private readonly IServiceScopeFactory _factory; + private readonly IOptions _options; + private readonly ILogger _logger; + + public MessageProducer(IServiceScopeFactory factory, IOptions options, ILogger logger) + { + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + using var scope = _factory.CreateScope(); + var bus = scope.ServiceProvider.GetRequiredService(); + + while (!cancellationToken.IsCancellationRequested) + { + var message = new Message(); + await bus.SendAsync(message); + _logger.LogProducedMessage(message.Id); + await Task.Delay(_options.Value.ProducerDelay, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Example05/Program.cs b/src/Example05/Program.cs new file mode 100644 index 0000000..7849e15 --- /dev/null +++ b/src/Example05/Program.cs @@ -0,0 +1,6 @@ +using Example05; + +var builder = Host.CreateApplicationBuilder(args); +builder.AddServices(); +var host = builder.Build(); +host.Run(); \ No newline at end of file diff --git a/src/Example05/Properties/launchSettings.json b/src/Example05/Properties/launchSettings.json new file mode 100644 index 0000000..c88cc0e --- /dev/null +++ b/src/Example05/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Example05": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Example05/appsettings.Development.json b/src/Example05/appsettings.Development.json new file mode 100644 index 0000000..5fe925e --- /dev/null +++ b/src/Example05/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Warning" + } + } +} \ No newline at end of file diff --git a/src/Example05/appsettings.json b/src/Example05/appsettings.json new file mode 100644 index 0000000..b6b0282 --- /dev/null +++ b/src/Example05/appsettings.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Warning" + }, + "Console": { + "FormatterName": "simple", + "FormatterOptions": { + "SingleLine": true, + "TimestampFormat": "HH:mm:ss.ffff " + } + } + }, + "Settings": { + "QueueName": "example05-queue", + "ConnectionString": "Endpoint=sb://namespace.service-bus.windows.net" + } +} \ No newline at end of file