From 39de82c50021fbd267a7ae0122c1cb082332d607 Mon Sep 17 00:00:00 2001 From: Maxwell Weru Date: Tue, 21 Nov 2023 19:44:59 +0300 Subject: [PATCH] Move non-IConfiguration logic from DefaultEventBusConfigurator to MandatoryEventBusConfigurator --- .../DefaultEventBusConfigurator.cs | 134 +-------------- .../MandatoryEventBusConfigurator.cs | 162 ++++++++++++++++++ .../DependencyInjection/EventBusBuilder.cs | 1 + ... => MandatoryEventBusConfiguratorTests.cs} | 29 +--- 4 files changed, 172 insertions(+), 154 deletions(-) create mode 100644 src/Tingle.EventBus/Configuration/MandatoryEventBusConfigurator.cs rename tests/Tingle.EventBus.Tests/Configurator/{DefaultEventBusConfiguratorTests.cs => MandatoryEventBusConfiguratorTests.cs} (86%) diff --git a/src/Tingle.EventBus/Configuration/DefaultEventBusConfigurator.cs b/src/Tingle.EventBus/Configuration/DefaultEventBusConfigurator.cs index f47c309a..76f21017 100644 --- a/src/Tingle.EventBus/Configuration/DefaultEventBusConfigurator.cs +++ b/src/Tingle.EventBus/Configuration/DefaultEventBusConfigurator.cs @@ -1,8 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using System.Diagnostics.CodeAnalysis; -using Tingle.EventBus.Serialization; using Tingle.EventBus.Transports; namespace Tingle.EventBus.Configuration; @@ -14,15 +12,12 @@ namespace Tingle.EventBus.Configuration; [RequiresUnreferencedCode(MessageStrings.BindingUnreferencedCodeMessage)] public class DefaultEventBusConfigurator : IEventBusConfigurator { - private readonly IHostEnvironment environment; private readonly IEventBusConfigurationProvider configurationProvider; /// Creates an instance of . - /// The instance. /// The instance. - public DefaultEventBusConfigurator(IHostEnvironment environment, IEventBusConfigurationProvider configurationProvider) + public DefaultEventBusConfigurator(IEventBusConfigurationProvider configurationProvider) { - this.environment = environment ?? throw new ArgumentNullException(nameof(environment)); this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider)); } @@ -51,132 +46,5 @@ public void Configure(EventRegistration registration, EventBusOptions options) { configuration.GetSection($"Consumers:{ecr.ConsumerType.FullName}").Bind(ecr); } - - // set transport name - ConfigureTransportName(registration, options); - - // set event name and kind - ConfigureEventName(registration, options.Naming); - ConfigureEntityKind(registration); - - // set the consumer names - ConfigureConsumerNames(registration, options.Naming); - - // set the serializer - ConfigureSerializer(registration); - } - - internal void ConfigureTransportName(EventRegistration reg, EventBusOptions options) - { - // If the event transport name has not been specified, attempt to get from the attribute - var type = reg.EventType; - reg.TransportName ??= type.GetCustomAttributes(false).OfType().SingleOrDefault()?.Name; - - // If the event transport name has not been set, try the default one - reg.TransportName ??= options.DefaultTransportName; - - // Set the transport name from the default, if not set - if (string.IsNullOrWhiteSpace(reg.TransportName)) - { - throw new InvalidOperationException($"Unable to set the transport for event '{type.FullName}'." - + $" Either set the '{nameof(options.DefaultTransportName)}' option" - + $" or use the '{typeof(EventTransportNameAttribute).FullName}' on the event."); - } - - // Ensure the transport name set has been registered - if (!options.TransportMap.ContainsKey(reg.TransportName)) - { - throw new InvalidOperationException($"Transport '{reg.TransportName}' on event '{type.FullName}' must be registered."); - } - } - - internal void ConfigureEventName(EventRegistration reg, EventBusNamingOptions options) - { - // set the event name, if not set - if (string.IsNullOrWhiteSpace(reg.EventName)) - { - var type = reg.EventType; - // prioritize the attribute if available, otherwise get the type name - var name = type.GetCustomAttributes(false).OfType().SingleOrDefault()?.EventName; - if (name == null) - { - var typeName = options.UseFullTypeNames ? type.FullName! : type.Name; - typeName = options.TrimCommonSuffixes(typeName); - name = typeName; - name = options.ApplyNamingConvention(name); - name = options.AppendScope(name); - name = options.ReplaceInvalidCharacters(name); - } - reg.EventName = name; - } - } - - internal void ConfigureEntityKind(EventRegistration reg) - { - // set the entity kind, if not set and there is an attribute - if (reg.EntityKind == null) - { - var type = reg.EventType; - var kind = type.GetCustomAttributes(false).OfType().SingleOrDefault()?.Kind; - if (kind != null) - { - reg.EntityKind = kind; - } - } - } - - internal void ConfigureConsumerNames(EventRegistration reg, EventBusNamingOptions options) - { - // ensure we have the event name set - if (string.IsNullOrWhiteSpace(reg.EventName)) - { - throw new InvalidOperationException($"The {nameof(reg.EventName)} for must be set before setting names of the consumer."); - } - - // prefix is either the one provided or the application name - var prefix = options.ConsumerNamePrefix ?? environment.ApplicationName; - - foreach (var ecr in reg.Consumers) - { - // set the consumer name, if not set - if (string.IsNullOrWhiteSpace(ecr.ConsumerName)) - { - var type = ecr.ConsumerType; - // prioritize the attribute if available, otherwise get the type name - var name = type.GetCustomAttributes(false).OfType().SingleOrDefault()?.ConsumerName; - if (name == null) - { - var typeName = options.UseFullTypeNames ? type.FullName! : type.Name; - typeName = options.TrimCommonSuffixes(typeName); - name = options.ConsumerNameSource switch - { - ConsumerNameSource.TypeName => typeName, - ConsumerNameSource.Prefix => prefix, - ConsumerNameSource.PrefixAndTypeName => $"{prefix}.{typeName}", - _ => throw new InvalidOperationException($"'{nameof(options.ConsumerNameSource)}.{options.ConsumerNameSource}' is not supported"), - }; - name = options.ApplyNamingConvention(name); - name = options.AppendScope(name); - name = options.ReplaceInvalidCharacters(name); - } - ecr.ConsumerName = name; - } - } - } - - internal void ConfigureSerializer(EventRegistration reg) - { - // If the event serializer has not been specified, attempt to get from the attribute - var attrs = reg.EventType.GetCustomAttributes(false); - reg.EventSerializerType ??= attrs.OfType().SingleOrDefault()?.SerializerType; - reg.EventSerializerType ??= typeof(IEventSerializer); // use the default when not provided - - // Ensure the serializer is either default or it implements IEventSerializer - if (reg.EventSerializerType != typeof(IEventSerializer) - && !typeof(IEventSerializer).IsAssignableFrom(reg.EventSerializerType)) - { - throw new InvalidOperationException($"The type '{reg.EventSerializerType.FullName}' is used as a serializer " - + $"but does not implement '{typeof(IEventSerializer).FullName}'"); - } } } diff --git a/src/Tingle.EventBus/Configuration/MandatoryEventBusConfigurator.cs b/src/Tingle.EventBus/Configuration/MandatoryEventBusConfigurator.cs new file mode 100644 index 00000000..34f9298b --- /dev/null +++ b/src/Tingle.EventBus/Configuration/MandatoryEventBusConfigurator.cs @@ -0,0 +1,162 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Tingle.EventBus.Serialization; +using Tingle.EventBus.Transports; + +namespace Tingle.EventBus.Configuration; + +/// +/// Mandatory implementation of . +/// +internal class MandatoryEventBusConfigurator : IEventBusConfigurator +{ + private readonly IHostEnvironment environment; + + /// Creates an instance of . + /// The instance. + public MandatoryEventBusConfigurator(IHostEnvironment environment) + { + this.environment = environment ?? throw new ArgumentNullException(nameof(environment)); + } + + /// + public void Configure(EventBusOptions options) { } + + /// + public void Configure(IConfiguration configuration, TOptions options) where TOptions : EventBusTransportOptions { } + + /// + public void Configure(EventRegistration registration, EventBusOptions options) + { + if (registration is null) throw new ArgumentNullException(nameof(registration)); + if (options is null) throw new ArgumentNullException(nameof(options)); + + // set transport name + ConfigureTransportName(registration, options); + + // set event name and kind + ConfigureEventName(registration, options.Naming); + ConfigureEntityKind(registration); + + // set the consumer names + ConfigureConsumerNames(registration, options.Naming); + + // set the serializer + ConfigureSerializer(registration); + } + + internal void ConfigureTransportName(EventRegistration reg, EventBusOptions options) + { + // If the event transport name has not been specified, attempt to get from the attribute + var type = reg.EventType; + reg.TransportName ??= type.GetCustomAttributes(false).OfType().SingleOrDefault()?.Name; + + // If the event transport name has not been set, try the default one + reg.TransportName ??= options.DefaultTransportName; + + // Set the transport name from the default, if not set + if (string.IsNullOrWhiteSpace(reg.TransportName)) + { + throw new InvalidOperationException($"Unable to set the transport for event '{type.FullName}'." + + $" Either set the '{nameof(options.DefaultTransportName)}' option" + + $" or use the '{typeof(EventTransportNameAttribute).FullName}' on the event."); + } + + // Ensure the transport name set has been registered + if (!options.TransportMap.ContainsKey(reg.TransportName)) + { + throw new InvalidOperationException($"Transport '{reg.TransportName}' on event '{type.FullName}' must be registered."); + } + } + + internal void ConfigureEventName(EventRegistration reg, EventBusNamingOptions options) + { + // set the event name, if not set + if (string.IsNullOrWhiteSpace(reg.EventName)) + { + var type = reg.EventType; + // prioritize the attribute if available, otherwise get the type name + var name = type.GetCustomAttributes(false).OfType().SingleOrDefault()?.EventName; + if (name == null) + { + var typeName = options.UseFullTypeNames ? type.FullName! : type.Name; + typeName = options.TrimCommonSuffixes(typeName); + name = typeName; + name = options.ApplyNamingConvention(name); + name = options.AppendScope(name); + name = options.ReplaceInvalidCharacters(name); + } + reg.EventName = name; + } + } + + internal void ConfigureEntityKind(EventRegistration reg) + { + // set the entity kind, if not set and there is an attribute + if (reg.EntityKind == null) + { + var type = reg.EventType; + var kind = type.GetCustomAttributes(false).OfType().SingleOrDefault()?.Kind; + if (kind != null) + { + reg.EntityKind = kind; + } + } + } + + internal void ConfigureConsumerNames(EventRegistration reg, EventBusNamingOptions options) + { + // ensure we have the event name set + if (string.IsNullOrWhiteSpace(reg.EventName)) + { + throw new InvalidOperationException($"The {nameof(reg.EventName)} for must be set before setting names of the consumer."); + } + + // prefix is either the one provided or the application name + var prefix = options.ConsumerNamePrefix ?? environment.ApplicationName; + + foreach (var ecr in reg.Consumers) + { + // set the consumer name, if not set + if (string.IsNullOrWhiteSpace(ecr.ConsumerName)) + { + var type = ecr.ConsumerType; + // prioritize the attribute if available, otherwise get the type name + var name = type.GetCustomAttributes(false).OfType().SingleOrDefault()?.ConsumerName; + if (name == null) + { + var typeName = options.UseFullTypeNames ? type.FullName! : type.Name; + typeName = options.TrimCommonSuffixes(typeName); + name = options.ConsumerNameSource switch + { + ConsumerNameSource.TypeName => typeName, + ConsumerNameSource.Prefix => prefix, + ConsumerNameSource.PrefixAndTypeName => $"{prefix}.{typeName}", + _ => throw new InvalidOperationException($"'{nameof(options.ConsumerNameSource)}.{options.ConsumerNameSource}' is not supported"), + }; + name = options.ApplyNamingConvention(name); + name = options.AppendScope(name); + name = options.ReplaceInvalidCharacters(name); + } + ecr.ConsumerName = name; + } + } + } + + internal void ConfigureSerializer(EventRegistration reg) + { + // If the event serializer has not been specified, attempt to get from the attribute + var attrs = reg.EventType.GetCustomAttributes(false); + reg.EventSerializerType ??= attrs.OfType().SingleOrDefault()?.SerializerType; + reg.EventSerializerType ??= typeof(IEventSerializer); // use the default when not provided + + // Ensure the serializer is either default or it implements IEventSerializer + if (reg.EventSerializerType != typeof(IEventSerializer) + && !typeof(IEventSerializer).IsAssignableFrom(reg.EventSerializerType)) + { + throw new InvalidOperationException($"The type '{reg.EventSerializerType.FullName}' is used as a serializer " + + $"but does not implement '{typeof(IEventSerializer).FullName}'"); + } + } +} diff --git a/src/Tingle.EventBus/DependencyInjection/EventBusBuilder.cs b/src/Tingle.EventBus/DependencyInjection/EventBusBuilder.cs index 5c740e92..0a7e6618 100644 --- a/src/Tingle.EventBus/DependencyInjection/EventBusBuilder.cs +++ b/src/Tingle.EventBus/DependencyInjection/EventBusBuilder.cs @@ -34,6 +34,7 @@ public EventBusBuilder(IServiceCollection services) Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddTransient(); + Services.AddSingleton(); // can be multiple do not use TryAdd*(...) } /// diff --git a/tests/Tingle.EventBus.Tests/Configurator/DefaultEventBusConfiguratorTests.cs b/tests/Tingle.EventBus.Tests/Configurator/MandatoryEventBusConfiguratorTests.cs similarity index 86% rename from tests/Tingle.EventBus.Tests/Configurator/DefaultEventBusConfiguratorTests.cs rename to tests/Tingle.EventBus.Tests/Configurator/MandatoryEventBusConfiguratorTests.cs index 6d71475c..b1f9d198 100644 --- a/tests/Tingle.EventBus.Tests/Configurator/DefaultEventBusConfiguratorTests.cs +++ b/tests/Tingle.EventBus.Tests/Configurator/MandatoryEventBusConfiguratorTests.cs @@ -1,18 +1,15 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Tingle.EventBus.Configuration; using Tingle.EventBus.Serialization; namespace Tingle.EventBus.Tests.Configurator; -public class DefaultEventBusConfiguratorTests +public class MandatoryEventBusConfiguratorTests { [Fact] public void ConfigureSerializer_UsesDefault() { - var configuration = new ConfigurationBuilder().Build(); - var configurationProvider = new DefaultEventBusConfigurationProvider(configuration); - var configurator = new DefaultEventBusConfigurator(new FakeHostEnvironment("app1"), configurationProvider); + var configurator = new MandatoryEventBusConfigurator(new FakeHostEnvironment("app1")); // when not set, use default var registration = new EventRegistration(typeof(TestEvent1)); @@ -24,9 +21,7 @@ public void ConfigureSerializer_UsesDefault() [Fact] public void ConfigureSerializer_RespectsAttribute() { - var configuration = new ConfigurationBuilder().Build(); - var configurationProvider = new DefaultEventBusConfigurationProvider(configuration); - var configurator = new DefaultEventBusConfigurator(new FakeHostEnvironment("app1"), configurationProvider); + var configurator = new MandatoryEventBusConfigurator(new FakeHostEnvironment("app1")); // attribute is respected var registration = new EventRegistration(typeof(TestEvent2)); @@ -38,9 +33,7 @@ public void ConfigureSerializer_RespectsAttribute() [Fact] public void ConfigureSerializer_Throws_InvalidOperationException() { - var configuration = new ConfigurationBuilder().Build(); - var configurationProvider = new DefaultEventBusConfigurationProvider(configuration); - var configurator = new DefaultEventBusConfigurator(new FakeHostEnvironment("app1"), configurationProvider); + var configurator = new MandatoryEventBusConfigurator(new FakeHostEnvironment("app1")); // attribute is respected var registration = new EventRegistration(typeof(TestEvent3)); @@ -63,9 +56,7 @@ public void ConfigureSerializer_Throws_InvalidOperationException() [InlineData(typeof(TestEvent2), true, "dev", NamingConvention.DotCase, "sample-event")] public void ConfigureEventName_Works(Type eventType, bool useFullTypeNames, string scope, NamingConvention namingConvention, string expected) { - var configuration = new ConfigurationBuilder().Build(); - var configurationProvider = new DefaultEventBusConfigurationProvider(configuration); - var configurator = new DefaultEventBusConfigurator(new FakeHostEnvironment("app1"), configurationProvider); + var configurator = new MandatoryEventBusConfigurator(new FakeHostEnvironment("app1")); var options = new EventBusOptions { }; options.Naming.Scope = scope; @@ -141,9 +132,7 @@ public void SetConsumerName_Works(Type eventType, NamingConvention namingConvention, string expected) { - var configuration = new ConfigurationBuilder().Build(); - var configurationProvider = new DefaultEventBusConfigurationProvider(configuration); - var configurator = new DefaultEventBusConfigurator(new FakeHostEnvironment("app1"), configurationProvider); + var configurator = new MandatoryEventBusConfigurator(new FakeHostEnvironment("app1")); var options = new EventBusOptions { }; options.Naming.Convention = namingConvention; @@ -166,9 +155,7 @@ public void SetConsumerName_Works(Type eventType, [InlineData(typeof(TestEvent3), EntityKind.Broadcast)] public void ConfigureEntityKind_Works(Type eventType, EntityKind? expected) { - var configuration = new ConfigurationBuilder().Build(); - var configurationProvider = new DefaultEventBusConfigurationProvider(configuration); - var configurator = new DefaultEventBusConfigurator(new FakeHostEnvironment("app1"), configurationProvider); + var configurator = new MandatoryEventBusConfigurator(new FakeHostEnvironment("app1")); var registration = new EventRegistration(eventType); configurator.ConfigureEntityKind(registration);