From b3cfacad643a4646057f334e40c19e40742bf5cc Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 3 Mar 2026 17:53:49 +0100 Subject: [PATCH 01/24] zxfv --- src/Dafda/Configuration/ProducerOptions.cs | 283 +++++++++--------- .../ProducerServiceCollectionExtensions.cs | 192 ++++++++---- src/Dafda/Producing/ProducerFactory.cs | 142 ++++----- .../Producing/ProducerFactoryException.cs | 17 +- 4 files changed, 350 insertions(+), 284 deletions(-) diff --git a/src/Dafda/Configuration/ProducerOptions.cs b/src/Dafda/Configuration/ProducerOptions.cs index 05c8b980..0ef71c5c 100644 --- a/src/Dafda/Configuration/ProducerOptions.cs +++ b/src/Dafda/Configuration/ProducerOptions.cs @@ -3,172 +3,159 @@ using Dafda.Serializing; using Microsoft.Extensions.Logging; -namespace Dafda.Configuration +namespace Dafda.Configuration; + +/// +/// Facilitates Dafda configuration in .NET applications using the . +/// +public sealed class ProducerOptions { + internal readonly ProducerConfigurationBuilder Builder = new(); + internal readonly OutgoingMessageRegistry OutgoingMessageRegistry = new(); + /// - /// Facilitates Dafda configuration in .NET applications using the . + /// Specify a custom implementation of the to use. /// - public sealed class ProducerOptions + /// The to use. + public void WithConfigurationSource(ConfigurationSource configurationSource) { - private readonly ProducerConfigurationBuilder _builder; - private readonly OutgoingMessageRegistry _outgoingMessageRegistry; - - internal ProducerOptions(ProducerConfigurationBuilder builder, OutgoingMessageRegistry outgoingMessageRegistry) - { - _builder = builder; - _outgoingMessageRegistry = outgoingMessageRegistry; - } - - /// - /// Specify a custom implementation of the to use. - /// - /// The to use. - public void WithConfigurationSource(ConfigurationSource configurationSource) - { - _builder.WithConfigurationSource(configurationSource); - } + Builder.WithConfigurationSource(configurationSource); + } - /// - /// Use as the configuration source. - /// - /// The configuration instance. - public void WithConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) - { - _builder.WithConfigurationSource(new DefaultConfigurationSource(configuration)); - } + /// + /// Use as the configuration source. + /// + /// The configuration instance. + public void WithConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) + { + Builder.WithConfigurationSource(new DefaultConfigurationSource(configuration)); + } - /// - /// Add a custom naming convention for converting configuration keys when - /// looking up keys in the . - /// - /// Use this to transform keys. - public void WithNamingConvention(Func converter) - { - _builder.WithNamingConvention(converter); - } + /// + /// Add a custom naming convention for converting configuration keys when + /// looking up keys in the . + /// + /// Use this to transform keys. + public void WithNamingConvention(Func converter) + { + Builder.WithNamingConvention(converter); + } - /// - /// Add default environment style naming convention. The configuration will attempt to - /// fetch keys from , using the following scheme: - /// - /// keys will be converted to uppercase. - /// any one or more of SPACE, TAB, ., and - will be converted to a single _. - /// the prefix will be prefixed (in uppercase) along with a _. - /// - /// - /// When configuring a consumer the WithEnvironmentStyle("app"), Dafda will attempt to find the - /// key APP_GROUP_ID in the . - /// - /// The prefix to use before keys. - /// Additional prefixes to use before keys. - public void WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) - { - _builder.WithEnvironmentStyle(prefix, additionalPrefixes); - } + /// + /// Add default environment style naming convention. The configuration will attempt to + /// fetch keys from , using the following scheme: + /// + /// keys will be converted to uppercase. + /// any one or more of SPACE, TAB, ., and - will be converted to a single _. + /// the prefix will be prefixed (in uppercase) along with a _. + /// + /// + /// When configuring a consumer the WithEnvironmentStyle("app"), Dafda will attempt to find the + /// key APP_GROUP_ID in the . + /// + /// The prefix to use before keys. + /// Additional prefixes to use before keys. + public void WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) + { + Builder.WithEnvironmentStyle(prefix, additionalPrefixes); + } - /// - /// Add a configuration key/value directly to the underlying Kafka consumer. - /// - /// The configuration key. - /// The configuration value. - public void WithConfiguration(string key, string value) - { - _builder.WithConfiguration(key, value); - } + /// + /// Add a configuration key/value directly to the underlying Kafka consumer. + /// + /// The configuration key. + /// The configuration value. + public void WithConfiguration(string key, string value) + { + Builder.WithConfiguration(key, value); + } - /// - /// A shorthand to set the bootstrap.servers Kafka configuration value. - /// - /// A list of bootstrap servers. - public void WithBootstrapServers(string bootstrapServers) - { - _builder.WithBootstrapServers(bootstrapServers); - } + /// + /// A shorthand to set the bootstrap.servers Kafka configuration value. + /// + /// A list of bootstrap servers. + public void WithBootstrapServers(string bootstrapServers) + { + Builder.WithBootstrapServers(bootstrapServers); + } - internal void WithKafkaProducerFactory(Func inlineFactory) - { - _builder.WithKafkaProducerFactory(inlineFactory); - } + internal void WithKafkaProducerFactory(Func inlineFactory) + { + Builder.WithKafkaProducerFactory(inlineFactory); + } - /// - /// Override the default Dafda implementation of . - /// - /// A custom implementation of . - public void WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) - { - _builder.WithMessageIdGenerator(messageIdGenerator); - } + /// + /// Override the default Dafda implementation of . + /// + /// A custom implementation of . + public void WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) + { + Builder.WithMessageIdGenerator(messageIdGenerator); + } - /// - /// Register outbox messages. - /// - /// The type of message going to the call. - /// The target topic. - /// The event type to use in the Dafda message envelope. - /// The key selector takes an instance of , - /// and returns a string of the Kafka partition key. - public void Register(string topic, string type, Func keySelector) where T : class - { - _outgoingMessageRegistry.Register(topic, type, keySelector); - } + /// + /// Register outbox messages. + /// + /// The type of message going to the call. + /// The target topic. + /// The event type to use in the Dafda message envelope. + /// The key selector takes an instance of , + /// and returns a string of the Kafka partition key. + public void Register(string topic, string type, Func keySelector) where T : class + { + OutgoingMessageRegistry.Register(topic, type, keySelector); + } - /// - /// Override the with a custom implementation - /// - /// A custom implementation of - public void WithDefaultPayloadSerializer(IPayloadSerializer payloadSerializer) - { - WithDefaultPayloadSerializer(() => payloadSerializer); - } + /// + /// Override the with a custom implementation + /// + /// A custom implementation of + public void WithDefaultPayloadSerializer(IPayloadSerializer payloadSerializer) + { + WithDefaultPayloadSerializer(() => payloadSerializer); + } - /// - /// Override the with a custom implementation - /// - /// A factory method that returns a custom implementation - /// of - /// - public void WithDefaultPayloadSerializer(Func payloadSerializerFactory) - { - _builder.WithDefaultPayloadSerializer(payloadSerializerFactory); - } + /// + /// Override the with a custom implementation + /// + /// A factory method that returns a custom implementation + /// of + /// + public void WithDefaultPayloadSerializer(Func payloadSerializerFactory) + { + Builder.WithDefaultPayloadSerializer(payloadSerializerFactory); + } - /// - /// Override the with a custom implementation for - /// the specified - /// - /// Name of the topic - /// A custom implementation of - public void WithPayloadSerializer(string topic, IPayloadSerializer payloadSerializer) - { - WithPayloadSerializer(topic, () => payloadSerializer); - } + /// + /// Override the with a custom implementation for + /// the specified + /// + /// Name of the topic + /// A custom implementation of + public void WithPayloadSerializer(string topic, IPayloadSerializer payloadSerializer) + { + WithPayloadSerializer(topic, () => payloadSerializer); + } - /// - /// Override the with a custom implementation for - /// the specified - /// - /// Name of the topic - /// A factory method that returns a custom implementation - /// of - /// - public void WithPayloadSerializer(string topic, Func payloadSerializerFactory) - { - _builder.WithPayloadSerializer(topic, payloadSerializerFactory); - } + /// + /// Override the with a custom implementation for + /// the specified + /// + /// Name of the topic + /// A factory method that returns a custom implementation + /// of + /// + public void WithPayloadSerializer(string topic, Func payloadSerializerFactory) + { + Builder.WithPayloadSerializer(topic, payloadSerializerFactory); + } - private class DefaultConfigurationSource : ConfigurationSource + private class DefaultConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) + : ConfigurationSource + { + public override string GetByKey(string key) { - private readonly Microsoft.Extensions.Configuration.IConfiguration _configuration; - - public DefaultConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) - { - _configuration = configuration; - } - - public override string GetByKey(string key) - { - return _configuration[key]; - } + return configuration[key]; } } } \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index 18b034c5..799ec577 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -4,79 +4,153 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Dafda.Configuration +namespace Dafda.Configuration; + +/// +public static class ProducerServiceCollectionExtensions { - /// - public static class ProducerServiceCollectionExtensions + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . + /// + /// NOTE: currently only a single producer can be configured per . + /// + /// The used in Startup. + /// Use this action to override Dafda and underlying Kafka configuration. + public static void AddProducerFor(this IServiceCollection services, Action options) + where TImplementation : class, TService + where TService : class { - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// NOTE: currently only a single producer can be configured per . - /// - /// The used in Startup. - /// Use this action to override Dafda and underlying Kafka configuration. - public static void AddProducerFor(this IServiceCollection services, Action options) - where TImplementation : class, TService - where TService : class - { - var factory = CreateProducerFactory(services, options); - services.AddTransient(provider => - CreateInstance(provider, factory)); - } + EnsureServiceNotAlreadyRegistered(services); - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// NOTE: currently only a single producer can be configured per . - /// - /// The used in Startup. - /// Use this action to override Dafda and underlying Kafka configuration. - public static void AddProducerFor(this IServiceCollection services, Action options) where TClient : class - { - var factory = CreateProducerFactory(services, options); - services.AddTransient(provider => - CreateInstance(provider, factory)); - } + var factory = AddOrGetRegisteredProducerFactory(services); + + var producerOptions = new ProducerOptions(); + options?.Invoke(producerOptions); + var producerConfiguration = producerOptions.Builder.Build(); + var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; + factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); - private static TImplementation CreateInstance(IServiceProvider provider, ProducerFactory factory) - { - var loggerFactory = provider.GetRequiredService(); - var producer = factory.GetFor(loggerFactory); - return ActivatorUtilities.CreateInstance(provider, producer); - } + services.AddTransient(provider => CreateInstance(provider, factory)); + } + + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . + /// + /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// + /// NOTE: currently only a single producer can be configured per . + /// + /// The used in Startup. + /// Factory that creates and configures using the built . + public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) + where TImplementation : class, TService + where TService : class + { + EnsureServiceNotAlreadyRegistered(services); - private static ProducerFactory CreateProducerFactory(IServiceCollection services, Action options) + services.AddTransient(provider => { - var outgoingMessageRegistry = new OutgoingMessageRegistry(); - var configurationBuilder = new ProducerConfigurationBuilder(); - var consumerOptions = new ProducerOptions(configurationBuilder, outgoingMessageRegistry); - options?.Invoke(consumerOptions); + var factory = AddOrGetRegisteredProducerFactory(services); - var producerConfiguration = configurationBuilder.Build(); + if (!factory.IsConfigured()) + { + var producerOptions = optionsFactory(provider); + var producerConfiguration = producerOptions.Builder.Build(); + var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; + factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); + } - var factory = AddOrGetRegisteredProducerFactory(services); - factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); - return factory; - } + return CreateInstance(provider, factory); + }); + } + + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . + /// + /// NOTE: currently only a single producer can be configured per . + /// + /// The used in Startup. + /// Use this action to override Dafda and underlying Kafka configuration. + public static void AddProducerFor(this IServiceCollection services, Action options) where TClient : class + { + EnsureServiceNotAlreadyRegistered(services); + + var factory = AddOrGetRegisteredProducerFactory(services); + + var producerOptions = new ProducerOptions(); + options?.Invoke(producerOptions); + var producerConfiguration = producerOptions.Builder.Build(); + var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; + factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); - private static ProducerFactory AddOrGetRegisteredProducerFactory(IServiceCollection services) + services.AddTransient(provider => CreateInstance(provider, factory)); + } + + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . + /// + /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// + /// NOTE: currently only a single producer can be configured per . + /// + /// The used in Startup. + /// Factory that creates and configures using the built . + public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) where TClient : class + { + EnsureServiceNotAlreadyRegistered(services); + + services.AddTransient(provider => { - var factory = services - .Where(x => x.ServiceType == typeof(ProducerFactory)) - .Select(x => x.ImplementationInstance) - .Cast() - .SingleOrDefault(); + var factory = AddOrGetRegisteredProducerFactory(services); - if (factory == null) + if (!factory.IsConfigured()) { - factory = new ProducerFactory(); - services.AddSingleton(factory); + var producerOptions = optionsFactory(provider); + var producerConfiguration = producerOptions.Builder.Build(); + var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; + factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); } - return factory; + return CreateInstance(provider, factory); + }); + } + + private static void EnsureServiceNotAlreadyRegistered(IServiceCollection services) where TService : class + { + if (services.Any(d => d.ServiceType == typeof(TService))) + { + throw new ProducerFactoryException( + $"A service registration for '{typeof(TService).FullName}' already exists. " + + "Dafda producers can only be registered once per service type. " + + "Remove the existing registration or use a different service type."); + } + } + + private static TImplementation CreateInstance(IServiceProvider provider, ProducerFactory factory) + { + var loggerFactory = provider.GetRequiredService(); + var producer = factory.GetFor(loggerFactory); + return ActivatorUtilities.CreateInstance(provider, producer); + } + + private static ProducerFactory AddOrGetRegisteredProducerFactory(IServiceCollection services) + { + var factory = services + .Where(x => x.ServiceType == typeof(ProducerFactory)) + .Select(x => x.ImplementationInstance) + .Cast() + .SingleOrDefault(); + + if (factory == null) + { + factory = new ProducerFactory(); + services.AddSingleton(factory); } + + return factory; } } \ No newline at end of file diff --git a/src/Dafda/Producing/ProducerFactory.cs b/src/Dafda/Producing/ProducerFactory.cs index bdf7f811..3e24f58d 100644 --- a/src/Dafda/Producing/ProducerFactory.cs +++ b/src/Dafda/Producing/ProducerFactory.cs @@ -3,99 +3,105 @@ using Dafda.Configuration; using Microsoft.Extensions.Logging; -namespace Dafda.Producing +namespace Dafda.Producing; + +internal sealed class ProducerFactory : IDisposable { - internal sealed class ProducerFactory : IDisposable - { - private readonly Dictionary _producerBuilders = new Dictionary(); + private readonly Dictionary _producerBuilders = new Dictionary(); - internal void ConfigureProducer(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) + internal void ConfigureProducer(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) + { + if (_producerBuilders.ContainsKey(producerName)) { - if (_producerBuilders.ContainsKey(producerName)) - { - throw new ProducerFactoryException($"A producer with the name \"{producerName}\" has already been configured. Producer names should be unique."); - } - - _producerBuilders.Add(producerName, new ProducerBuilder( - producerName: producerName, - configuration: configuration, - messageRegistry: outgoingMessageRegistry - )); + throw new ProducerFactoryException($"A producer with the name \"{producerName}\" has already been configured. Producer names should be unique."); } - internal static string GetKeyNameOf() => $"__INTERNAL__FOR_CLIENT__{typeof(TClient).FullName}"; + _producerBuilders.Add(producerName, new ProducerBuilder( + producerName: producerName, + configuration: configuration, + messageRegistry: outgoingMessageRegistry + )); + } + + internal static string GetKeyNameOf() => $"__INTERNAL__FOR_CLIENT__{typeof(TClient).FullName}"; - internal void ConfigureProducerFor(ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) + internal void ConfigureProducerFor(ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) + { + var producerName = GetKeyNameOf(); + ConfigureProducer(producerName, configuration, outgoingMessageRegistry); + } + + internal bool IsConfigured() + { + var producerName = GetKeyNameOf(); + + return _producerBuilders.ContainsKey(producerName); + } + + public Producer Get(string producerName, ILoggerFactory loggerFactory) + { + if (_producerBuilders.TryGetValue(producerName, out var builder)) { - var producerName = GetKeyNameOf(); - ConfigureProducer(producerName, configuration, outgoingMessageRegistry); + return builder.Build(loggerFactory); } - public Producer Get(string producerName, ILoggerFactory loggerFactory) + return null; + } + + public Producer GetFor(ILoggerFactory loggerFactory) + { + var producerName = GetKeyNameOf(); + return Get(producerName, loggerFactory); + } + + public void Dispose() + { + if (_producerBuilders != null) { - if (_producerBuilders.TryGetValue(producerName, out var builder)) + foreach (var registration in _producerBuilders.Values) { - return builder.Build(loggerFactory); + registration.Dispose(); } - - return null; } + } - public Producer GetFor(ILoggerFactory loggerFactory) - { - var producerName = GetKeyNameOf(); - return Get(producerName, loggerFactory); - } + private class ProducerBuilder : IDisposable + { + private readonly OutgoingMessageRegistry _messageRegistry; + private readonly Func _kafkaProducerFactory; + private readonly MessageIdGenerator _messageIdGenerator; + private readonly string _producerName; + private KafkaProducer _kafkaProducer; - public void Dispose() + public ProducerBuilder(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry messageRegistry) { - if (_producerBuilders != null) - { - foreach (var registration in _producerBuilders.Values) - { - registration.Dispose(); - } - } + _messageRegistry = messageRegistry; + _producerName = producerName; + _kafkaProducerFactory = configuration.KafkaProducerFactory; + _messageIdGenerator = configuration.MessageIdGenerator; } - private class ProducerBuilder : IDisposable + public Producer Build(ILoggerFactory loggerFactory) { - private readonly OutgoingMessageRegistry _messageRegistry; - private readonly Func _kafkaProducerFactory; - private readonly MessageIdGenerator _messageIdGenerator; - private readonly string _producerName; - private KafkaProducer _kafkaProducer; - - public ProducerBuilder(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry messageRegistry) + if (_kafkaProducer is null) { - _messageRegistry = messageRegistry; - _producerName = producerName; - _kafkaProducerFactory = configuration.KafkaProducerFactory; - _messageIdGenerator = configuration.MessageIdGenerator; + _kafkaProducer = _kafkaProducerFactory(loggerFactory); } - public Producer Build(ILoggerFactory loggerFactory) - { - if (_kafkaProducer is null) - { - _kafkaProducer = _kafkaProducerFactory(loggerFactory); - } - - var producer = new Producer( - kafkaProducer: _kafkaProducer, - outgoingMessageRegistry: _messageRegistry, - messageIdGenerator: _messageIdGenerator - ); + var producer = new Producer( + kafkaProducer: _kafkaProducer, + outgoingMessageRegistry: _messageRegistry, + messageIdGenerator: _messageIdGenerator + ); - producer.Name = _producerName; + producer.Name = _producerName; - return producer; - } + return producer; + } - public void Dispose() - { - _kafkaProducer?.Dispose(); - } + public void Dispose() + { + _kafkaProducer?.Dispose(); } } } \ No newline at end of file diff --git a/src/Dafda/Producing/ProducerFactoryException.cs b/src/Dafda/Producing/ProducerFactoryException.cs index 328ad444..314cec02 100644 --- a/src/Dafda/Producing/ProducerFactoryException.cs +++ b/src/Dafda/Producing/ProducerFactoryException.cs @@ -1,16 +1,15 @@ using System; using Dafda.Configuration; -namespace Dafda.Producing +namespace Dafda.Producing; + +/// +/// Exception thrown when multiple identical producers are added via +/// . +/// +public sealed class ProducerFactoryException : Exception { - /// - /// Exception thrown when multiple identical producers are added via - /// - /// - public sealed class ProducerFactoryException : Exception + internal ProducerFactoryException(string message) : base(message) { - internal ProducerFactoryException(string message) : base(message) - { - } } } \ No newline at end of file From 54dc98a96debb7bcd3305b97a16211b5160b142a Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Fri, 6 Mar 2026 12:09:49 +0100 Subject: [PATCH 02/24] rdet --- src/Dafda/Configuration/ProducerOptions.cs | 271 +++++++++--------- .../ProducerServiceCollectionExtensions.cs | 230 +++++++-------- src/Dafda/Producing/ProducerFactory.cs | 147 +++++----- 3 files changed, 313 insertions(+), 335 deletions(-) diff --git a/src/Dafda/Configuration/ProducerOptions.cs b/src/Dafda/Configuration/ProducerOptions.cs index 0ef71c5c..56cf6824 100644 --- a/src/Dafda/Configuration/ProducerOptions.cs +++ b/src/Dafda/Configuration/ProducerOptions.cs @@ -3,159 +3,160 @@ using Dafda.Serializing; using Microsoft.Extensions.Logging; -namespace Dafda.Configuration; - -/// -/// Facilitates Dafda configuration in .NET applications using the . -/// -public sealed class ProducerOptions +namespace Dafda.Configuration { - internal readonly ProducerConfigurationBuilder Builder = new(); - internal readonly OutgoingMessageRegistry OutgoingMessageRegistry = new(); - /// - /// Specify a custom implementation of the to use. + /// Facilitates Dafda configuration in .NET applications using the . /// - /// The to use. - public void WithConfigurationSource(ConfigurationSource configurationSource) + public sealed class ProducerOptions { - Builder.WithConfigurationSource(configurationSource); - } + internal readonly ProducerConfigurationBuilder Builder = new(); + internal readonly OutgoingMessageRegistry OutgoingMessageRegistry = new(); + + /// + /// Specify a custom implementation of the to use. + /// + /// The to use. + public void WithConfigurationSource(ConfigurationSource configurationSource) + { + Builder.WithConfigurationSource(configurationSource); + } - /// - /// Use as the configuration source. - /// - /// The configuration instance. - public void WithConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) - { - Builder.WithConfigurationSource(new DefaultConfigurationSource(configuration)); - } + /// + /// Use as the configuration source. + /// + /// The configuration instance. + public void WithConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) + { + Builder.WithConfigurationSource(new DefaultConfigurationSource(configuration)); + } - /// - /// Add a custom naming convention for converting configuration keys when - /// looking up keys in the . - /// - /// Use this to transform keys. - public void WithNamingConvention(Func converter) - { - Builder.WithNamingConvention(converter); - } + /// + /// Add a custom naming convention for converting configuration keys when + /// looking up keys in the . + /// + /// Use this to transform keys. + public void WithNamingConvention(Func converter) + { + Builder.WithNamingConvention(converter); + } - /// - /// Add default environment style naming convention. The configuration will attempt to - /// fetch keys from , using the following scheme: - /// - /// keys will be converted to uppercase. - /// any one or more of SPACE, TAB, ., and - will be converted to a single _. - /// the prefix will be prefixed (in uppercase) along with a _. - /// - /// - /// When configuring a consumer the WithEnvironmentStyle("app"), Dafda will attempt to find the - /// key APP_GROUP_ID in the . - /// - /// The prefix to use before keys. - /// Additional prefixes to use before keys. - public void WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) - { - Builder.WithEnvironmentStyle(prefix, additionalPrefixes); - } + /// + /// Add default environment style naming convention. The configuration will attempt to + /// fetch keys from , using the following scheme: + /// + /// keys will be converted to uppercase. + /// any one or more of SPACE, TAB, ., and - will be converted to a single _. + /// the prefix will be prefixed (in uppercase) along with a _. + /// + /// + /// When configuring a consumer the WithEnvironmentStyle("app"), Dafda will attempt to find the + /// key APP_GROUP_ID in the . + /// + /// The prefix to use before keys. + /// Additional prefixes to use before keys. + public void WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) + { + Builder.WithEnvironmentStyle(prefix, additionalPrefixes); + } - /// - /// Add a configuration key/value directly to the underlying Kafka consumer. - /// - /// The configuration key. - /// The configuration value. - public void WithConfiguration(string key, string value) - { - Builder.WithConfiguration(key, value); - } + /// + /// Add a configuration key/value directly to the underlying Kafka consumer. + /// + /// The configuration key. + /// The configuration value. + public void WithConfiguration(string key, string value) + { + Builder.WithConfiguration(key, value); + } - /// - /// A shorthand to set the bootstrap.servers Kafka configuration value. - /// - /// A list of bootstrap servers. - public void WithBootstrapServers(string bootstrapServers) - { - Builder.WithBootstrapServers(bootstrapServers); - } + /// + /// A shorthand to set the bootstrap.servers Kafka configuration value. + /// + /// A list of bootstrap servers. + public void WithBootstrapServers(string bootstrapServers) + { + Builder.WithBootstrapServers(bootstrapServers); + } - internal void WithKafkaProducerFactory(Func inlineFactory) - { - Builder.WithKafkaProducerFactory(inlineFactory); - } + internal void WithKafkaProducerFactory(Func inlineFactory) + { + Builder.WithKafkaProducerFactory(inlineFactory); + } - /// - /// Override the default Dafda implementation of . - /// - /// A custom implementation of . - public void WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) - { - Builder.WithMessageIdGenerator(messageIdGenerator); - } + /// + /// Override the default Dafda implementation of . + /// + /// A custom implementation of . + public void WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) + { + Builder.WithMessageIdGenerator(messageIdGenerator); + } - /// - /// Register outbox messages. - /// - /// The type of message going to the call. - /// The target topic. - /// The event type to use in the Dafda message envelope. - /// The key selector takes an instance of , - /// and returns a string of the Kafka partition key. - public void Register(string topic, string type, Func keySelector) where T : class - { - OutgoingMessageRegistry.Register(topic, type, keySelector); - } + /// + /// Register outbox messages. + /// + /// The type of message going to the call. + /// The target topic. + /// The event type to use in the Dafda message envelope. + /// The key selector takes an instance of , + /// and returns a string of the Kafka partition key. + public void Register(string topic, string type, Func keySelector) where T : class + { + OutgoingMessageRegistry.Register(topic, type, keySelector); + } - /// - /// Override the with a custom implementation - /// - /// A custom implementation of - public void WithDefaultPayloadSerializer(IPayloadSerializer payloadSerializer) - { - WithDefaultPayloadSerializer(() => payloadSerializer); - } + /// + /// Override the with a custom implementation + /// + /// A custom implementation of + public void WithDefaultPayloadSerializer(IPayloadSerializer payloadSerializer) + { + WithDefaultPayloadSerializer(() => payloadSerializer); + } - /// - /// Override the with a custom implementation - /// - /// A factory method that returns a custom implementation - /// of - /// - public void WithDefaultPayloadSerializer(Func payloadSerializerFactory) - { - Builder.WithDefaultPayloadSerializer(payloadSerializerFactory); - } + /// + /// Override the with a custom implementation + /// + /// A factory method that returns a custom implementation + /// of + /// + public void WithDefaultPayloadSerializer(Func payloadSerializerFactory) + { + Builder.WithDefaultPayloadSerializer(payloadSerializerFactory); + } - /// - /// Override the with a custom implementation for - /// the specified - /// - /// Name of the topic - /// A custom implementation of - public void WithPayloadSerializer(string topic, IPayloadSerializer payloadSerializer) - { - WithPayloadSerializer(topic, () => payloadSerializer); - } + /// + /// Override the with a custom implementation for + /// the specified + /// + /// Name of the topic + /// A custom implementation of + public void WithPayloadSerializer(string topic, IPayloadSerializer payloadSerializer) + { + WithPayloadSerializer(topic, () => payloadSerializer); + } - /// - /// Override the with a custom implementation for - /// the specified - /// - /// Name of the topic - /// A factory method that returns a custom implementation - /// of - /// - public void WithPayloadSerializer(string topic, Func payloadSerializerFactory) - { - Builder.WithPayloadSerializer(topic, payloadSerializerFactory); - } + /// + /// Override the with a custom implementation for + /// the specified + /// + /// Name of the topic + /// A factory method that returns a custom implementation + /// of + /// + public void WithPayloadSerializer(string topic, Func payloadSerializerFactory) + { + Builder.WithPayloadSerializer(topic, payloadSerializerFactory); + } - private class DefaultConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) - : ConfigurationSource - { - public override string GetByKey(string key) + private class DefaultConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) + : ConfigurationSource { - return configuration[key]; + public override string GetByKey(string key) + { + return configuration[key]; + } } } } \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index 799ec577..ca67a34a 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -4,153 +4,129 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Dafda.Configuration; - -/// -public static class ProducerServiceCollectionExtensions +namespace Dafda.Configuration { - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// NOTE: currently only a single producer can be configured per . - /// - /// The used in Startup. - /// Use this action to override Dafda and underlying Kafka configuration. - public static void AddProducerFor(this IServiceCollection services, Action options) - where TImplementation : class, TService - where TService : class + /// + public static class ProducerServiceCollectionExtensions { - EnsureServiceNotAlreadyRegistered(services); + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . + /// + /// NOTE: currently only a single producer can be configured per . + /// + /// The used in Startup. + /// Use this action to override Dafda and underlying Kafka configuration. + public static void AddProducerFor(this IServiceCollection services, Action options) + where TImplementation : class, TService + where TService : class + { + EnsureServiceNotAlreadyRegistered(services); - var factory = AddOrGetRegisteredProducerFactory(services); + var factory = AddOrGetRegisteredProducerFactory(services); - var producerOptions = new ProducerOptions(); - options?.Invoke(producerOptions); - var producerConfiguration = producerOptions.Builder.Build(); - var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; - factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); + var producerOptions = new ProducerOptions(); + options?.Invoke(producerOptions); + var producerConfiguration = producerOptions.Builder.Build(); + var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; + factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); - services.AddTransient(provider => CreateInstance(provider, factory)); - } + services.AddTransient(provider => CreateInstance(provider, factory)); + } - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// Use this overload when configuration depends on other services (for example, IConfiguration). - /// - /// NOTE: currently only a single producer can be configured per . - /// - /// The used in Startup. - /// Factory that creates and configures using the built . - public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) - where TImplementation : class, TService - where TService : class - { - EnsureServiceNotAlreadyRegistered(services); + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . + /// + /// NOTE: currently only a single producer can be configured per . + /// + /// The used in Startup. + /// Use this action to override Dafda and underlying Kafka configuration. + public static void AddProducerFor(this IServiceCollection services, Action options) where TClient : class + { + AddProducerFor(services, options); + } - services.AddTransient(provider => + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . + /// + /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// + /// NOTE: currently only a single producer can be configured per . + /// + /// The used in Startup. + /// Factory that creates and configures using the built . + public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) + where TImplementation : class, TService + where TService : class { - var factory = AddOrGetRegisteredProducerFactory(services); + EnsureServiceNotAlreadyRegistered(services); - if (!factory.IsConfigured()) + services.AddTransient(provider => { - var producerOptions = optionsFactory(provider); - var producerConfiguration = producerOptions.Builder.Build(); - var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; - factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); - } - - return CreateInstance(provider, factory); - }); - } - - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// NOTE: currently only a single producer can be configured per . - /// - /// The used in Startup. - /// Use this action to override Dafda and underlying Kafka configuration. - public static void AddProducerFor(this IServiceCollection services, Action options) where TClient : class - { - EnsureServiceNotAlreadyRegistered(services); - - var factory = AddOrGetRegisteredProducerFactory(services); - - var producerOptions = new ProducerOptions(); - options?.Invoke(producerOptions); - var producerConfiguration = producerOptions.Builder.Build(); - var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; - factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); - - services.AddTransient(provider => CreateInstance(provider, factory)); - } - - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// Use this overload when configuration depends on other services (for example, IConfiguration). - /// - /// NOTE: currently only a single producer can be configured per . - /// - /// The used in Startup. - /// Factory that creates and configures using the built . - public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) where TClient : class - { - EnsureServiceNotAlreadyRegistered(services); + var factory = AddOrGetRegisteredProducerFactory(services); + + if (!factory.IsConfigured()) + { + var producerOptions = optionsFactory(provider); + var producerConfiguration = producerOptions.Builder.Build(); + var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; + factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); + } + + return CreateInstance(provider, factory); + }); + } - services.AddTransient(provider => + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . + /// + /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// + /// NOTE: currently only a single producer can be configured per . + /// + /// The used in Startup. + /// Factory that creates and configures using the built . + public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) where TClient : class { - var factory = AddOrGetRegisteredProducerFactory(services); - - if (!factory.IsConfigured()) + AddProducerFor(services, optionsFactory); + } + + private static void EnsureServiceNotAlreadyRegistered(IServiceCollection services) where TService : class + { + if (services.Any(d => d.ServiceType == typeof(TService))) { - var producerOptions = optionsFactory(provider); - var producerConfiguration = producerOptions.Builder.Build(); - var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; - factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); + throw new ProducerFactoryException( + $"A service registration for '{typeof(TService).FullName}' already exists. " + + "Dafda producers can only be registered once per service type. " + + "Remove the existing registration or use a different service type."); } + } - return CreateInstance(provider, factory); - }); - } - - private static void EnsureServiceNotAlreadyRegistered(IServiceCollection services) where TService : class - { - if (services.Any(d => d.ServiceType == typeof(TService))) + private static TImplementation CreateInstance(IServiceProvider provider, ProducerFactory factory) { - throw new ProducerFactoryException( - $"A service registration for '{typeof(TService).FullName}' already exists. " + - "Dafda producers can only be registered once per service type. " + - "Remove the existing registration or use a different service type."); + var loggerFactory = provider.GetRequiredService(); + var producer = factory.GetFor(loggerFactory); + return ActivatorUtilities.CreateInstance(provider, producer); } - } - private static TImplementation CreateInstance(IServiceProvider provider, ProducerFactory factory) - { - var loggerFactory = provider.GetRequiredService(); - var producer = factory.GetFor(loggerFactory); - return ActivatorUtilities.CreateInstance(provider, producer); - } + private static ProducerFactory AddOrGetRegisteredProducerFactory(IServiceCollection services) + { + var factory = services + .Where(x => x.ServiceType == typeof(ProducerFactory)) + .Select(x => x.ImplementationInstance) + .Cast() + .SingleOrDefault(); - private static ProducerFactory AddOrGetRegisteredProducerFactory(IServiceCollection services) - { - var factory = services - .Where(x => x.ServiceType == typeof(ProducerFactory)) - .Select(x => x.ImplementationInstance) - .Cast() - .SingleOrDefault(); + if (factory == null) + { + factory = new ProducerFactory(); + services.AddSingleton(factory); + } - if (factory == null) - { - factory = new ProducerFactory(); - services.AddSingleton(factory); + return factory; } - - return factory; } } \ No newline at end of file diff --git a/src/Dafda/Producing/ProducerFactory.cs b/src/Dafda/Producing/ProducerFactory.cs index 3e24f58d..ea1c7e78 100644 --- a/src/Dafda/Producing/ProducerFactory.cs +++ b/src/Dafda/Producing/ProducerFactory.cs @@ -3,105 +3,106 @@ using Dafda.Configuration; using Microsoft.Extensions.Logging; -namespace Dafda.Producing; - -internal sealed class ProducerFactory : IDisposable +namespace Dafda.Producing { - private readonly Dictionary _producerBuilders = new Dictionary(); - - internal void ConfigureProducer(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) + internal sealed class ProducerFactory : IDisposable { - if (_producerBuilders.ContainsKey(producerName)) + private readonly Dictionary _producerBuilders = new Dictionary(); + + internal void ConfigureProducer(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) { - throw new ProducerFactoryException($"A producer with the name \"{producerName}\" has already been configured. Producer names should be unique."); - } + if (_producerBuilders.ContainsKey(producerName)) + { + throw new ProducerFactoryException($"A producer with the name \"{producerName}\" has already been configured. Producer names should be unique."); + } - _producerBuilders.Add(producerName, new ProducerBuilder( - producerName: producerName, - configuration: configuration, - messageRegistry: outgoingMessageRegistry - )); - } + _producerBuilders.Add(producerName, new ProducerBuilder( + producerName: producerName, + configuration: configuration, + messageRegistry: outgoingMessageRegistry + )); + } - internal static string GetKeyNameOf() => $"__INTERNAL__FOR_CLIENT__{typeof(TClient).FullName}"; + internal static string GetKeyNameOf() => $"__INTERNAL__FOR_CLIENT__{typeof(TClient).FullName}"; - internal void ConfigureProducerFor(ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) - { - var producerName = GetKeyNameOf(); - ConfigureProducer(producerName, configuration, outgoingMessageRegistry); - } + internal void ConfigureProducerFor(ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) + { + var producerName = GetKeyNameOf(); + ConfigureProducer(producerName, configuration, outgoingMessageRegistry); + } - internal bool IsConfigured() - { - var producerName = GetKeyNameOf(); - - return _producerBuilders.ContainsKey(producerName); - } - - public Producer Get(string producerName, ILoggerFactory loggerFactory) - { - if (_producerBuilders.TryGetValue(producerName, out var builder)) + internal bool IsConfigured() { - return builder.Build(loggerFactory); + var producerName = GetKeyNameOf(); + + return _producerBuilders.ContainsKey(producerName); } - return null; - } - - public Producer GetFor(ILoggerFactory loggerFactory) - { - var producerName = GetKeyNameOf(); - return Get(producerName, loggerFactory); - } - - public void Dispose() - { - if (_producerBuilders != null) + public Producer Get(string producerName, ILoggerFactory loggerFactory) { - foreach (var registration in _producerBuilders.Values) + if (_producerBuilders.TryGetValue(producerName, out var builder)) { - registration.Dispose(); + return builder.Build(loggerFactory); } + + return null; } - } - private class ProducerBuilder : IDisposable - { - private readonly OutgoingMessageRegistry _messageRegistry; - private readonly Func _kafkaProducerFactory; - private readonly MessageIdGenerator _messageIdGenerator; - private readonly string _producerName; - private KafkaProducer _kafkaProducer; + public Producer GetFor(ILoggerFactory loggerFactory) + { + var producerName = GetKeyNameOf(); + return Get(producerName, loggerFactory); + } - public ProducerBuilder(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry messageRegistry) + public void Dispose() { - _messageRegistry = messageRegistry; - _producerName = producerName; - _kafkaProducerFactory = configuration.KafkaProducerFactory; - _messageIdGenerator = configuration.MessageIdGenerator; + if (_producerBuilders != null) + { + foreach (var registration in _producerBuilders.Values) + { + registration.Dispose(); + } + } } - public Producer Build(ILoggerFactory loggerFactory) + private class ProducerBuilder : IDisposable { - if (_kafkaProducer is null) + private readonly OutgoingMessageRegistry _messageRegistry; + private readonly Func _kafkaProducerFactory; + private readonly MessageIdGenerator _messageIdGenerator; + private readonly string _producerName; + private KafkaProducer _kafkaProducer; + + public ProducerBuilder(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry messageRegistry) { - _kafkaProducer = _kafkaProducerFactory(loggerFactory); + _messageRegistry = messageRegistry; + _producerName = producerName; + _kafkaProducerFactory = configuration.KafkaProducerFactory; + _messageIdGenerator = configuration.MessageIdGenerator; } - var producer = new Producer( - kafkaProducer: _kafkaProducer, - outgoingMessageRegistry: _messageRegistry, - messageIdGenerator: _messageIdGenerator - ); + public Producer Build(ILoggerFactory loggerFactory) + { + if (_kafkaProducer is null) + { + _kafkaProducer = _kafkaProducerFactory(loggerFactory); + } - producer.Name = _producerName; + var producer = new Producer( + kafkaProducer: _kafkaProducer, + outgoingMessageRegistry: _messageRegistry, + messageIdGenerator: _messageIdGenerator + ); - return producer; - } + producer.Name = _producerName; - public void Dispose() - { - _kafkaProducer?.Dispose(); + return producer; + } + + public void Dispose() + { + _kafkaProducer?.Dispose(); + } } } } \ No newline at end of file From 75bb72234040ba50e03380867a7880022c8d78b5 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Fri, 6 Mar 2026 13:42:25 +0100 Subject: [PATCH 03/24] drg --- .../Builders/ProducerFactoryBuilder.cs | 12 -- .../Configuration/OutboxProducerOptions.cs | 2 +- .../OutboxServiceCollectionExtensions.cs | 2 +- .../Configuration/ProducerConfiguration.cs | 23 ++-- .../ProducerConfigurationBuilder.cs | 12 +- src/Dafda/Configuration/ProducerOptions.cs | 2 +- .../ProducerServiceCollectionExtensions.cs | 80 +++++++------ src/Dafda/Producing/ProducerFactory.cs | 108 ------------------ 8 files changed, 64 insertions(+), 177 deletions(-) delete mode 100644 src/Dafda.Tests/Builders/ProducerFactoryBuilder.cs delete mode 100644 src/Dafda/Producing/ProducerFactory.cs diff --git a/src/Dafda.Tests/Builders/ProducerFactoryBuilder.cs b/src/Dafda.Tests/Builders/ProducerFactoryBuilder.cs deleted file mode 100644 index df22c0f9..00000000 --- a/src/Dafda.Tests/Builders/ProducerFactoryBuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Dafda.Producing; - -namespace Dafda.Tests.Builders -{ - internal class ProducerFactoryBuilder - { - public ProducerFactory Build() - { - return new ProducerFactory(); - } - } -} \ No newline at end of file diff --git a/src/Dafda/Configuration/OutboxProducerOptions.cs b/src/Dafda/Configuration/OutboxProducerOptions.cs index 2c788f33..b162be5f 100644 --- a/src/Dafda/Configuration/OutboxProducerOptions.cs +++ b/src/Dafda/Configuration/OutboxProducerOptions.cs @@ -88,7 +88,7 @@ public void WithBootstrapServers(string bootstrapServers) _builder.WithBootstrapServers(bootstrapServers); } - internal void WithKafkaProducerFactory(Func inlineFactory) + internal void WithKafkaProducerFactory(Func inlineFactory) { _builder.WithKafkaProducerFactory(inlineFactory); } diff --git a/src/Dafda/Configuration/OutboxServiceCollectionExtensions.cs b/src/Dafda/Configuration/OutboxServiceCollectionExtensions.cs index fb4f4d77..382cc47c 100644 --- a/src/Dafda/Configuration/OutboxServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/OutboxServiceCollectionExtensions.cs @@ -54,7 +54,7 @@ public static void AddOutboxProducer(this IServiceCollection services, Action(); var loggerFactory = provider.GetRequiredService(); - var kafkaProducer = configuration.KafkaProducerFactory(loggerFactory); + var kafkaProducer = configuration.KafkaProducerFactory(provider); var producer = new OutboxProducer(kafkaProducer); var outboxDispatcher = new OutboxDispatcher(loggerFactory, outboxUnitOfWorkFactory, producer); diff --git a/src/Dafda/Configuration/ProducerConfiguration.cs b/src/Dafda/Configuration/ProducerConfiguration.cs index 15e4cea0..af961572 100644 --- a/src/Dafda/Configuration/ProducerConfiguration.cs +++ b/src/Dafda/Configuration/ProducerConfiguration.cs @@ -3,19 +3,14 @@ using Dafda.Producing; using Microsoft.Extensions.Logging; -namespace Dafda.Configuration -{ - internal class ProducerConfiguration - { - public ProducerConfiguration(IDictionary configuration, MessageIdGenerator messageIdGenerator, Func kafkaProducerFactory) - { - KafkaConfiguration = configuration; - MessageIdGenerator = messageIdGenerator; - KafkaProducerFactory = kafkaProducerFactory; - } +namespace Dafda.Configuration; - public IDictionary KafkaConfiguration { get; } - public MessageIdGenerator MessageIdGenerator { get; } - public Func KafkaProducerFactory { get; } - } +internal class ProducerConfiguration( + IDictionary configuration, + MessageIdGenerator messageIdGenerator, + Func kafkaProducerFactory) +{ + public IDictionary KafkaConfiguration { get; } = configuration; + public MessageIdGenerator MessageIdGenerator { get; } = messageIdGenerator; + public Func KafkaProducerFactory { get; } = kafkaProducerFactory; } \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerConfigurationBuilder.cs b/src/Dafda/Configuration/ProducerConfigurationBuilder.cs index 28936dc6..e8c39ce1 100644 --- a/src/Dafda/Configuration/ProducerConfigurationBuilder.cs +++ b/src/Dafda/Configuration/ProducerConfigurationBuilder.cs @@ -7,6 +7,8 @@ namespace Dafda.Configuration { + using Microsoft.Extensions.DependencyInjection; + internal sealed class ProducerConfigurationBuilder { private static readonly string[] DefaultConfigurationKeys = @@ -31,7 +33,7 @@ internal sealed class ProducerConfigurationBuilder private ConfigurationSource _configurationSource = ConfigurationSource.Null; private MessageIdGenerator _messageIdGenerator = MessageIdGenerator.Default; - private Func _kafkaProducerFactory; + private Func _kafkaProducerFactory; private static readonly IPayloadSerializer _defaultPayloadSerializer = new DefaultPayloadSerializer(); private readonly TopicPayloadSerializerRegistry _topicPayloadSerializerRegistry = new TopicPayloadSerializerRegistry(() => _defaultPayloadSerializer); @@ -82,7 +84,7 @@ public ProducerConfigurationBuilder WithMessageIdGenerator(MessageIdGenerator me return this; } - internal ProducerConfigurationBuilder WithKafkaProducerFactory(Func inlineFactory) + internal ProducerConfigurationBuilder WithKafkaProducerFactory(Func inlineFactory) { _kafkaProducerFactory = inlineFactory; return this; @@ -112,7 +114,11 @@ internal ProducerConfiguration Build() if (_kafkaProducerFactory == null) { - _kafkaProducerFactory = loggerFactory => new KafkaProducer(loggerFactory, configurations, _topicPayloadSerializerRegistry); + _kafkaProducerFactory = provider => + { + var loggerFactory = provider.GetRequiredService(); + return new KafkaProducer(loggerFactory, configurations, _topicPayloadSerializerRegistry); + }; } return new ProducerConfiguration( diff --git a/src/Dafda/Configuration/ProducerOptions.cs b/src/Dafda/Configuration/ProducerOptions.cs index 56cf6824..1638db3b 100644 --- a/src/Dafda/Configuration/ProducerOptions.cs +++ b/src/Dafda/Configuration/ProducerOptions.cs @@ -79,7 +79,7 @@ public void WithBootstrapServers(string bootstrapServers) Builder.WithBootstrapServers(bootstrapServers); } - internal void WithKafkaProducerFactory(Func inlineFactory) + internal void WithKafkaProducerFactory(Func inlineFactory) { Builder.WithKafkaProducerFactory(inlineFactory); } diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index ca67a34a..109eb0c4 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -23,15 +23,17 @@ public static void AddProducerFor(this IServiceCollec { EnsureServiceNotAlreadyRegistered(services); - var factory = AddOrGetRegisteredProducerFactory(services); - var producerOptions = new ProducerOptions(); options?.Invoke(producerOptions); var producerConfiguration = producerOptions.Builder.Build(); var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; - factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); - - services.AddTransient(provider => CreateInstance(provider, factory)); + var producerProvider = new ProducerProvider(producerConfiguration, outgoingMessageRegistry); + + services.AddTransient(provider => + { + var producer = producerProvider.CreateProducer(provider); + return ActivatorUtilities.CreateInstance(provider, producer); + }); } /// @@ -63,19 +65,19 @@ public static void AddProducerFor(this IServiceCollec { EnsureServiceNotAlreadyRegistered(services); + services.AddSingleton(provider => + { + var producerOptions = optionsFactory(provider); + var producerConfiguration = producerOptions.Builder.Build(); + var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; + return new ProducerProvider(producerConfiguration, outgoingMessageRegistry); + }); + services.AddTransient(provider => { - var factory = AddOrGetRegisteredProducerFactory(services); - - if (!factory.IsConfigured()) - { - var producerOptions = optionsFactory(provider); - var producerConfiguration = producerOptions.Builder.Build(); - var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; - factory.ConfigureProducerFor(producerConfiguration, outgoingMessageRegistry); - } - - return CreateInstance(provider, factory); + var producerProvider = provider.GetRequiredService>(); + var producer = producerProvider.CreateProducer(provider); + return ActivatorUtilities.CreateInstance(provider, producer); }); } @@ -99,34 +101,38 @@ private static void EnsureServiceNotAlreadyRegistered(IServiceCollecti if (services.Any(d => d.ServiceType == typeof(TService))) { throw new ProducerFactoryException( - $"A service registration for '{typeof(TService).FullName}' already exists. " + - "Dafda producers can only be registered once per service type. " + - "Remove the existing registration or use a different service type."); + $"A producer with the type \"{typeof(TService).FullName}\" has already been registered." + + $" Each producer should be registered with a unique service type."); } } + } + + internal class ProducerProvider( + ProducerConfiguration configuration, + OutgoingMessageRegistry messageRegistry) + : IDisposable + { + private KafkaProducer _kafkaProducer; - private static TImplementation CreateInstance(IServiceProvider provider, ProducerFactory factory) - { - var loggerFactory = provider.GetRequiredService(); - var producer = factory.GetFor(loggerFactory); - return ActivatorUtilities.CreateInstance(provider, producer); - } - - private static ProducerFactory AddOrGetRegisteredProducerFactory(IServiceCollection services) + public Producer CreateProducer(IServiceProvider provider) { - var factory = services - .Where(x => x.ServiceType == typeof(ProducerFactory)) - .Select(x => x.ImplementationInstance) - .Cast() - .SingleOrDefault(); + _kafkaProducer ??= configuration.KafkaProducerFactory(provider); - if (factory == null) + var producer = new Producer( + kafkaProducer: _kafkaProducer, + outgoingMessageRegistry: messageRegistry, + messageIdGenerator: configuration.MessageIdGenerator + ) { - factory = new ProducerFactory(); - services.AddSingleton(factory); - } + Name = typeof(TImplementation).FullName + }; + + return producer; + } - return factory; + public void Dispose() + { + _kafkaProducer?.Dispose(); // to do why? it's shared across all producer instances created by this provider, so we can't dispose it after each producer is created. Instead, we dispose it when the provider itself is disposed, which happens when the application shuts down. } } } \ No newline at end of file diff --git a/src/Dafda/Producing/ProducerFactory.cs b/src/Dafda/Producing/ProducerFactory.cs deleted file mode 100644 index ea1c7e78..00000000 --- a/src/Dafda/Producing/ProducerFactory.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using Dafda.Configuration; -using Microsoft.Extensions.Logging; - -namespace Dafda.Producing -{ - internal sealed class ProducerFactory : IDisposable - { - private readonly Dictionary _producerBuilders = new Dictionary(); - - internal void ConfigureProducer(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) - { - if (_producerBuilders.ContainsKey(producerName)) - { - throw new ProducerFactoryException($"A producer with the name \"{producerName}\" has already been configured. Producer names should be unique."); - } - - _producerBuilders.Add(producerName, new ProducerBuilder( - producerName: producerName, - configuration: configuration, - messageRegistry: outgoingMessageRegistry - )); - } - - internal static string GetKeyNameOf() => $"__INTERNAL__FOR_CLIENT__{typeof(TClient).FullName}"; - - internal void ConfigureProducerFor(ProducerConfiguration configuration, OutgoingMessageRegistry outgoingMessageRegistry) - { - var producerName = GetKeyNameOf(); - ConfigureProducer(producerName, configuration, outgoingMessageRegistry); - } - - internal bool IsConfigured() - { - var producerName = GetKeyNameOf(); - - return _producerBuilders.ContainsKey(producerName); - } - - public Producer Get(string producerName, ILoggerFactory loggerFactory) - { - if (_producerBuilders.TryGetValue(producerName, out var builder)) - { - return builder.Build(loggerFactory); - } - - return null; - } - - public Producer GetFor(ILoggerFactory loggerFactory) - { - var producerName = GetKeyNameOf(); - return Get(producerName, loggerFactory); - } - - public void Dispose() - { - if (_producerBuilders != null) - { - foreach (var registration in _producerBuilders.Values) - { - registration.Dispose(); - } - } - } - - private class ProducerBuilder : IDisposable - { - private readonly OutgoingMessageRegistry _messageRegistry; - private readonly Func _kafkaProducerFactory; - private readonly MessageIdGenerator _messageIdGenerator; - private readonly string _producerName; - private KafkaProducer _kafkaProducer; - - public ProducerBuilder(string producerName, ProducerConfiguration configuration, OutgoingMessageRegistry messageRegistry) - { - _messageRegistry = messageRegistry; - _producerName = producerName; - _kafkaProducerFactory = configuration.KafkaProducerFactory; - _messageIdGenerator = configuration.MessageIdGenerator; - } - - public Producer Build(ILoggerFactory loggerFactory) - { - if (_kafkaProducer is null) - { - _kafkaProducer = _kafkaProducerFactory(loggerFactory); - } - - var producer = new Producer( - kafkaProducer: _kafkaProducer, - outgoingMessageRegistry: _messageRegistry, - messageIdGenerator: _messageIdGenerator - ); - - producer.Name = _producerName; - - return producer; - } - - public void Dispose() - { - _kafkaProducer?.Dispose(); - } - } - } -} \ No newline at end of file From cb40e0dacf33ba7faef74a5351f22fe58930c4e4 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 10 Mar 2026 10:26:30 +0100 Subject: [PATCH 04/24] dfg --- .../ProducerServiceCollectionExtensions.cs | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index 109eb0c4..e3bd2c5a 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -2,7 +2,6 @@ using System.Linq; using Dafda.Producing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace Dafda.Configuration { @@ -21,19 +20,16 @@ public static void AddProducerFor(this IServiceCollec where TImplementation : class, TService where TService : class { - EnsureServiceNotAlreadyRegistered(services); + EnsureServiceIsNotAlreadyRegistered(services); - var producerOptions = new ProducerOptions(); - options?.Invoke(producerOptions); - var producerConfiguration = producerOptions.Builder.Build(); - var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; - var producerProvider = new ProducerProvider(producerConfiguration, outgoingMessageRegistry); - - services.AddTransient(provider => + services.AddSingleton(_ => { - var producer = producerProvider.CreateProducer(provider); - return ActivatorUtilities.CreateInstance(provider, producer); + var producerOptions = new ProducerOptions(); + options?.Invoke(producerOptions); + return new ProducerProvider(producerOptions); }); + + services.AddTransient(CreateImplementation); } /// @@ -63,22 +59,15 @@ public static void AddProducerFor(this IServiceCollec where TImplementation : class, TService where TService : class { - EnsureServiceNotAlreadyRegistered(services); + EnsureServiceIsNotAlreadyRegistered(services); services.AddSingleton(provider => { var producerOptions = optionsFactory(provider); - var producerConfiguration = producerOptions.Builder.Build(); - var outgoingMessageRegistry = producerOptions.OutgoingMessageRegistry; - return new ProducerProvider(producerConfiguration, outgoingMessageRegistry); + return new ProducerProvider(producerOptions); }); - services.AddTransient(provider => - { - var producerProvider = provider.GetRequiredService>(); - var producer = producerProvider.CreateProducer(provider); - return ActivatorUtilities.CreateInstance(provider, producer); - }); + services.AddTransient(CreateImplementation); } /// @@ -96,7 +85,7 @@ public static void AddProducerFor(this IServiceCollection services, Fun AddProducerFor(services, optionsFactory); } - private static void EnsureServiceNotAlreadyRegistered(IServiceCollection services) where TService : class + private static void EnsureServiceIsNotAlreadyRegistered(IServiceCollection services) where TService : class { if (services.Any(d => d.ServiceType == typeof(TService))) { @@ -105,23 +94,37 @@ private static void EnsureServiceNotAlreadyRegistered(IServiceCollecti $" Each producer should be registered with a unique service type."); } } + + private static TService CreateImplementation(IServiceProvider provider) + where TImplementation : class, TService + where TService : class + { + var producerProvider = provider.GetRequiredService>(); + var producer = producerProvider.CreateProducer(provider); + return ActivatorUtilities.CreateInstance(provider, producer); + } } - internal class ProducerProvider( - ProducerConfiguration configuration, - OutgoingMessageRegistry messageRegistry) - : IDisposable + internal class ProducerProvider : IDisposable { + private readonly ProducerConfiguration _configuration; + private readonly OutgoingMessageRegistry _messageRegistry; private KafkaProducer _kafkaProducer; + public ProducerProvider(ProducerOptions options) + { + _configuration = options.Builder.Build(); + _messageRegistry = options.OutgoingMessageRegistry; + } + public Producer CreateProducer(IServiceProvider provider) { - _kafkaProducer ??= configuration.KafkaProducerFactory(provider); + _kafkaProducer ??= _configuration.KafkaProducerFactory(provider); var producer = new Producer( kafkaProducer: _kafkaProducer, - outgoingMessageRegistry: messageRegistry, - messageIdGenerator: configuration.MessageIdGenerator + outgoingMessageRegistry: _messageRegistry, + messageIdGenerator: _configuration.MessageIdGenerator ) { Name = typeof(TImplementation).FullName @@ -132,7 +135,7 @@ public Producer CreateProducer(IServiceProvider provider) public void Dispose() { - _kafkaProducer?.Dispose(); // to do why? it's shared across all producer instances created by this provider, so we can't dispose it after each producer is created. Instead, we dispose it when the provider itself is disposed, which happens when the application shuts down. + _kafkaProducer?.Dispose(); } } } \ No newline at end of file From 87dea16ca9b3cf3196abdfa70e2a6510a6af51b5 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 10 Mar 2026 10:34:02 +0100 Subject: [PATCH 05/24] dfrg --- .../TestServiceCollectionExtensions.cs | 26 +----- .../Producing/TestProducerFactory.cs | 90 ------------------- .../ProducerServiceCollectionExtensions.cs | 6 +- 3 files changed, 7 insertions(+), 115 deletions(-) delete mode 100644 src/Dafda.Tests/Producing/TestProducerFactory.cs diff --git a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs index ad3a7f21..d251ab0e 100644 --- a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs +++ b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs @@ -9,22 +9,6 @@ namespace Dafda.Tests.Configuration { public class TestServiceCollectionExtensions { - [Fact] - public void registers_a_producer_factory() - { - var services = new ServiceCollection(); - - services.AddProducerFor(options => - { - options.WithBootstrapServers("dummy"); - }); - - var provider = services.BuildServiceProvider(); - var producerFactory = provider.GetService(); - - Assert.NotNull(producerFactory); - } - [Fact] public async Task Can_produce_message() { @@ -70,7 +54,7 @@ public void registers_a_typed_producer() Assert.NotNull(messageSender.ADependency); Assert.Equal("hello one", messageSender.ADependency.Message); - Assert.Equal(ProducerFactory.GetKeyNameOf(), messageSender.Producer.Name); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); } [Fact] @@ -88,7 +72,7 @@ public void resolves_a_typed_producer_for_an_abstract_service() Assert.NotNull(messageSender); Assert.NotNull(messageSender.Producer); - Assert.Equal(ProducerFactory.GetKeyNameOf(), messageSender.Producer.Name); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); } [Fact] @@ -118,7 +102,7 @@ public void registers_multiple_typed_producer() Assert.NotNull(messageSenderOne.ADependency); Assert.Equal("hello one", messageSenderOne.ADependency.Message); - Assert.Equal(ProducerFactory.GetKeyNameOf(), messageSenderOne.Producer.Name); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSenderOne.Producer.Name); var messageSenderTwo = provider.GetRequiredService(); @@ -127,9 +111,7 @@ public void registers_multiple_typed_producer() Assert.NotNull(messageSenderTwo.ADependency); Assert.Equal("hello two", messageSenderTwo.ADependency.Message); - Assert.Equal(ProducerFactory.GetKeyNameOf(), messageSenderTwo.Producer.Name); - - Assert.NotEqual(messageSenderOne.Producer.Name, messageSenderTwo.Producer.Name); + Assert.Equal(messageSenderTwo.Producer.Name, messageSenderTwo.Producer.Name); } [Fact] diff --git a/src/Dafda.Tests/Producing/TestProducerFactory.cs b/src/Dafda.Tests/Producing/TestProducerFactory.cs deleted file mode 100644 index 6bd0b4a1..00000000 --- a/src/Dafda.Tests/Producing/TestProducerFactory.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Dafda.Producing; -using Dafda.Tests.Builders; -using Dafda.Tests.TestDoubles; -using Microsoft.Extensions.Logging.Abstractions; -using Xunit; - -namespace Dafda.Tests.Producing -{ - public class TestProducerFactory - { - [Fact] - public void returns_expected_when_nothing_has_been_registered() - { - var sut = new ProducerFactoryBuilder().Build(); - var result = sut.Get("foo", NullLoggerFactory.Instance); - - Assert.Null(result); - } - - [Fact] - public void returns_expected_when_getting_by_a_known_name() - { - var producerConfigurationStub = A.ValidProducerConfiguration.Build(); - var outgoingMessageRegistryStub = new OutgoingMessageRegistry(); - - var sut = new ProducerFactoryBuilder().Build(); - sut.ConfigureProducer("foo", producerConfigurationStub, outgoingMessageRegistryStub); - - var result = sut.Get("foo", NullLoggerFactory.Instance); - - Assert.IsType(result); - Assert.NotNull(result); - } - - [Fact] - public void returns_expected_when_getting_by_an_unknown_name() - { - var producerConfigurationStub = A.ValidProducerConfiguration.Build(); - var outgoingMessageRegistryStub = new OutgoingMessageRegistry(); - - var sut = new ProducerFactoryBuilder().Build(); - sut.ConfigureProducer("foo", producerConfigurationStub, outgoingMessageRegistryStub); - - var result = sut.Get("bar", NullLoggerFactory.Instance); - - Assert.Null(result); - } - - [Fact] - public void throws_expected_when_adding_multiple_producers_with_same_name() - { - var sut = new ProducerFactoryBuilder().Build(); - - sut.ConfigureProducer( - producerName: "foo", - configuration: A.ValidProducerConfiguration.Build(), - outgoingMessageRegistry: new OutgoingMessageRegistry() - ); - - Assert.Throws(() => sut.ConfigureProducer( - producerName: "foo", - configuration: A.ValidProducerConfiguration.Build(), - outgoingMessageRegistry: new OutgoingMessageRegistry() - )); - } - - [Fact] - public void when_disposing_the_factory_all_kafka_producers_are_also_disposed() - { - var spy = new KafkaProducerSpy(); - - using (var sut = new ProducerFactoryBuilder().Build()) - { - var producerConfiguration = A.ValidProducerConfiguration - .WithKafkaProducerFactory(_ => spy) - .Build(); - - sut.ConfigureProducer( - producerName: "foo", - configuration: producerConfiguration, - outgoingMessageRegistry: new OutgoingMessageRegistry() - ); - - sut.Get("foo", NullLoggerFactory.Instance); - } - - Assert.True(spy.WasDisposed); - } - } -} \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index e3bd2c5a..3fb6e80c 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -20,7 +20,7 @@ public static void AddProducerFor(this IServiceCollec where TImplementation : class, TService where TService : class { - EnsureServiceIsNotAlreadyRegistered(services); + EnsureServiceIsNotAlreadyRegistered(services); services.AddSingleton(_ => { @@ -59,7 +59,7 @@ public static void AddProducerFor(this IServiceCollec where TImplementation : class, TService where TService : class { - EnsureServiceIsNotAlreadyRegistered(services); + EnsureServiceIsNotAlreadyRegistered(services); services.AddSingleton(provider => { @@ -95,7 +95,7 @@ private static void EnsureServiceIsNotAlreadyRegistered(IServiceCollec } } - private static TService CreateImplementation(IServiceProvider provider) + private static TImplementation CreateImplementation(IServiceProvider provider) where TImplementation : class, TService where TService : class { From bb01d3abd4bcb71c28feede74172309f8b906d92 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 10 Mar 2026 13:47:08 +0100 Subject: [PATCH 06/24] sd --- .../Configuration/ProducerServiceCollectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index 3fb6e80c..a0d0a885 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -20,7 +20,7 @@ public static void AddProducerFor(this IServiceCollec where TImplementation : class, TService where TService : class { - EnsureServiceIsNotAlreadyRegistered(services); + EnsureServiceIsNotAlreadyRegistered(services); services.AddSingleton(_ => { @@ -59,7 +59,7 @@ public static void AddProducerFor(this IServiceCollec where TImplementation : class, TService where TService : class { - EnsureServiceIsNotAlreadyRegistered(services); + EnsureServiceIsNotAlreadyRegistered(services); services.AddSingleton(provider => { From 454d33539f30afcb49b62ee3da251f3fb4f5b499 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 10 Mar 2026 17:19:04 +0100 Subject: [PATCH 07/24] fdg --- .../Configuration/ProducerServiceCollectionExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index a0d0a885..c9d57bf6 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -20,7 +20,7 @@ public static void AddProducerFor(this IServiceCollec where TImplementation : class, TService where TService : class { - EnsureServiceIsNotAlreadyRegistered(services); + ThrowIfProducerServiceAlreadyRegisteredFor(services); services.AddSingleton(_ => { @@ -59,7 +59,7 @@ public static void AddProducerFor(this IServiceCollec where TImplementation : class, TService where TService : class { - EnsureServiceIsNotAlreadyRegistered(services); + ThrowIfProducerServiceAlreadyRegisteredFor(services); services.AddSingleton(provider => { @@ -85,7 +85,7 @@ public static void AddProducerFor(this IServiceCollection services, Fun AddProducerFor(services, optionsFactory); } - private static void EnsureServiceIsNotAlreadyRegistered(IServiceCollection services) where TService : class + private static void ThrowIfProducerServiceAlreadyRegisteredFor(IServiceCollection services) where TService : class { if (services.Any(d => d.ServiceType == typeof(TService))) { From 4fc4cd0d13269924ac511c4caba98a8d2d7bf602 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 10 Mar 2026 17:29:41 +0100 Subject: [PATCH 08/24] sdfg --- .../ProducerServiceCollectionExtensions.cs | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index c9d57bf6..24207169 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -5,14 +5,15 @@ namespace Dafda.Configuration { - /// + /// + /// Extension methods for registering Kafka producers with the Microsoft dependency injection container. + /// public static class ProducerServiceCollectionExtensions { /// /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// NOTE: currently only a single producer can be configured per . + /// as . Each must be unique — + /// duplicate registrations will throw a . /// /// The used in Startup. /// Use this action to override Dafda and underlying Kafka configuration. @@ -26,17 +27,16 @@ public static void AddProducerFor(this IServiceCollec { var producerOptions = new ProducerOptions(); options?.Invoke(producerOptions); - return new ProducerProvider(producerOptions); + return new ProducerFactory(producerOptions); }); - services.AddTransient(CreateImplementation); + services.AddTransient(CreateProducerService); } /// /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// NOTE: currently only a single producer can be configured per . + /// as . Each must be unique — + /// duplicate registrations will throw a . /// /// The used in Startup. /// Use this action to override Dafda and underlying Kafka configuration. @@ -47,11 +47,10 @@ public static void AddProducerFor(this IServiceCollection services, Act /// /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . - /// - /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// as . Each must be unique — + /// duplicate registrations will throw a . /// - /// NOTE: currently only a single producer can be configured per . + /// Use this overload when configuration depends on other services (for example, IConfiguration). /// /// The used in Startup. /// Factory that creates and configures using the built . @@ -63,20 +62,19 @@ public static void AddProducerFor(this IServiceCollec services.AddSingleton(provider => { - var producerOptions = optionsFactory(provider); - return new ProducerProvider(producerOptions); + var options = optionsFactory(provider); + return new ProducerFactory(options); }); - services.AddTransient(CreateImplementation); + services.AddTransient(CreateProducerService); } /// /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . + /// as . Each must be unique — + /// duplicate registrations will throw a . /// - /// Use this overload when configuration depends on other services (for example, IConfiguration). - /// - /// NOTE: currently only a single producer can be configured per . + /// Use this overload when configuration depends on other services (for example, IConfiguration). /// /// The used in Startup. /// Factory that creates and configures using the built . @@ -90,34 +88,28 @@ private static void ThrowIfProducerServiceAlreadyRegisteredFor(IServic if (services.Any(d => d.ServiceType == typeof(TService))) { throw new ProducerFactoryException( - $"A producer with the type \"{typeof(TService).FullName}\" has already been registered." + - $" Each producer should be registered with a unique service type."); + $"A producer has already been registered for service type \"{typeof(TService).FullName}\". Each producer must use a unique service type."); } } - private static TImplementation CreateImplementation(IServiceProvider provider) + private static TImplementation CreateProducerService(IServiceProvider provider) where TImplementation : class, TService where TService : class { - var producerProvider = provider.GetRequiredService>(); - var producer = producerProvider.CreateProducer(provider); + var producerFactory = provider.GetRequiredService>(); + var producer = producerFactory.CreateProducerInstance(provider); return ActivatorUtilities.CreateInstance(provider, producer); } } - internal class ProducerProvider : IDisposable + internal class ProducerFactory(ProducerOptions options) : IDisposable { - private readonly ProducerConfiguration _configuration; - private readonly OutgoingMessageRegistry _messageRegistry; + private readonly ProducerConfiguration _configuration = options.Builder.Build(); + private readonly OutgoingMessageRegistry _messageRegistry = options.OutgoingMessageRegistry; private KafkaProducer _kafkaProducer; - public ProducerProvider(ProducerOptions options) - { - _configuration = options.Builder.Build(); - _messageRegistry = options.OutgoingMessageRegistry; - } - public Producer CreateProducer(IServiceProvider provider) + public Producer CreateProducerInstance(IServiceProvider provider) { _kafkaProducer ??= _configuration.KafkaProducerFactory(provider); From 6a0542d37c42e844baa30ffe91bc4fbaeb29d749 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 10 Mar 2026 17:30:15 +0100 Subject: [PATCH 09/24] sf --- .../ProducerServiceCollectionExtensions.cs | 207 +++++++++--------- 1 file changed, 103 insertions(+), 104 deletions(-) diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index 24207169..f72eef98 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -3,131 +3,130 @@ using Dafda.Producing; using Microsoft.Extensions.DependencyInjection; -namespace Dafda.Configuration +namespace Dafda.Configuration; + +/// +/// Extension methods for registering Kafka producers with the Microsoft dependency injection container. +/// +public static class ProducerServiceCollectionExtensions { /// - /// Extension methods for registering Kafka producers with the Microsoft dependency injection container. + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . Each must be unique — + /// duplicate registrations will throw a . /// - public static class ProducerServiceCollectionExtensions + /// The used in Startup. + /// Use this action to override Dafda and underlying Kafka configuration. + public static void AddProducerFor(this IServiceCollection services, Action options) + where TImplementation : class, TService + where TService : class { - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . Each must be unique — - /// duplicate registrations will throw a . - /// - /// The used in Startup. - /// Use this action to override Dafda and underlying Kafka configuration. - public static void AddProducerFor(this IServiceCollection services, Action options) - where TImplementation : class, TService - where TService : class - { - ThrowIfProducerServiceAlreadyRegisteredFor(services); + ThrowIfProducerServiceAlreadyRegisteredFor(services); - services.AddSingleton(_ => - { - var producerOptions = new ProducerOptions(); - options?.Invoke(producerOptions); - return new ProducerFactory(producerOptions); - }); + services.AddSingleton(_ => + { + var producerOptions = new ProducerOptions(); + options?.Invoke(producerOptions); + return new ProducerFactory(producerOptions); + }); - services.AddTransient(CreateProducerService); - } + services.AddTransient(CreateProducerService); + } - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . Each must be unique — - /// duplicate registrations will throw a . - /// - /// The used in Startup. - /// Use this action to override Dafda and underlying Kafka configuration. - public static void AddProducerFor(this IServiceCollection services, Action options) where TClient : class - { - AddProducerFor(services, options); - } + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . Each must be unique — + /// duplicate registrations will throw a . + /// + /// The used in Startup. + /// Use this action to override Dafda and underlying Kafka configuration. + public static void AddProducerFor(this IServiceCollection services, Action options) where TClient : class + { + AddProducerFor(services, options); + } - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . Each must be unique — - /// duplicate registrations will throw a . - /// - /// Use this overload when configuration depends on other services (for example, IConfiguration). - /// - /// The used in Startup. - /// Factory that creates and configures using the built . - public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) - where TImplementation : class, TService - where TService : class - { - ThrowIfProducerServiceAlreadyRegisteredFor(services); + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . Each must be unique — + /// duplicate registrations will throw a . + /// + /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// + /// The used in Startup. + /// Factory that creates and configures using the built . + public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) + where TImplementation : class, TService + where TService : class + { + ThrowIfProducerServiceAlreadyRegisteredFor(services); - services.AddSingleton(provider => - { - var options = optionsFactory(provider); - return new ProducerFactory(options); - }); + services.AddSingleton(provider => + { + var options = optionsFactory(provider); + return new ProducerFactory(options); + }); - services.AddTransient(CreateProducerService); - } + services.AddTransient(CreateProducerService); + } - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . Each must be unique — - /// duplicate registrations will throw a . - /// - /// Use this overload when configuration depends on other services (for example, IConfiguration). - /// - /// The used in Startup. - /// Factory that creates and configures using the built . - public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) where TClient : class - { - AddProducerFor(services, optionsFactory); - } + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . Each must be unique — + /// duplicate registrations will throw a . + /// + /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// + /// The used in Startup. + /// Factory that creates and configures using the built . + public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) where TClient : class + { + AddProducerFor(services, optionsFactory); + } - private static void ThrowIfProducerServiceAlreadyRegisteredFor(IServiceCollection services) where TService : class + private static void ThrowIfProducerServiceAlreadyRegisteredFor(IServiceCollection services) where TService : class + { + if (services.Any(d => d.ServiceType == typeof(TService))) { - if (services.Any(d => d.ServiceType == typeof(TService))) - { - throw new ProducerFactoryException( - $"A producer has already been registered for service type \"{typeof(TService).FullName}\". Each producer must use a unique service type."); - } + throw new ProducerFactoryException( + $"A producer has already been registered for service type \"{typeof(TService).FullName}\". Each producer must use a unique service type."); } + } - private static TImplementation CreateProducerService(IServiceProvider provider) - where TImplementation : class, TService - where TService : class - { - var producerFactory = provider.GetRequiredService>(); - var producer = producerFactory.CreateProducerInstance(provider); - return ActivatorUtilities.CreateInstance(provider, producer); - } + private static TImplementation CreateProducerService(IServiceProvider provider) + where TImplementation : class, TService + where TService : class + { + var producerFactory = provider.GetRequiredService>(); + var producer = producerFactory.CreateProducerInstance(provider); + return ActivatorUtilities.CreateInstance(provider, producer); } +} - internal class ProducerFactory(ProducerOptions options) : IDisposable - { - private readonly ProducerConfiguration _configuration = options.Builder.Build(); - private readonly OutgoingMessageRegistry _messageRegistry = options.OutgoingMessageRegistry; - private KafkaProducer _kafkaProducer; +internal class ProducerFactory(ProducerOptions options) : IDisposable +{ + private readonly ProducerConfiguration _configuration = options.Builder.Build(); + private readonly OutgoingMessageRegistry _messageRegistry = options.OutgoingMessageRegistry; + private KafkaProducer _kafkaProducer; - public Producer CreateProducerInstance(IServiceProvider provider) - { - _kafkaProducer ??= _configuration.KafkaProducerFactory(provider); + public Producer CreateProducerInstance(IServiceProvider provider) + { + _kafkaProducer ??= _configuration.KafkaProducerFactory(provider); - var producer = new Producer( - kafkaProducer: _kafkaProducer, - outgoingMessageRegistry: _messageRegistry, - messageIdGenerator: _configuration.MessageIdGenerator - ) - { - Name = typeof(TImplementation).FullName - }; + var producer = new Producer( + kafkaProducer: _kafkaProducer, + outgoingMessageRegistry: _messageRegistry, + messageIdGenerator: _configuration.MessageIdGenerator + ) + { + Name = typeof(TImplementation).FullName + }; - return producer; - } + return producer; + } - public void Dispose() - { - _kafkaProducer?.Dispose(); - } + public void Dispose() + { + _kafkaProducer?.Dispose(); } } \ No newline at end of file From 12519660ff82ccc997e4c38279c2469b8f446348 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 10 Mar 2026 19:33:08 +0100 Subject: [PATCH 10/24] sdf --- .../ConsumerConfigurationBase.cs | 6 +- .../ProducerConfigurationBuilder.cs | 202 +++++++------ src/Dafda/Configuration/ProducerOptions.cs | 272 +++++++++--------- 3 files changed, 238 insertions(+), 242 deletions(-) diff --git a/src/Dafda/Configuration/ConsumerConfigurationBase.cs b/src/Dafda/Configuration/ConsumerConfigurationBase.cs index b1fd24c6..ab005bc9 100644 --- a/src/Dafda/Configuration/ConsumerConfigurationBase.cs +++ b/src/Dafda/Configuration/ConsumerConfigurationBase.cs @@ -1,8 +1,8 @@ -using Dafda.Consuming.Interfaces; -using Dafda.Consuming; -using System; +using System; using System.Collections.Generic; using System.Text; +using Dafda.Consuming; +using Dafda.Consuming.Interfaces; namespace Dafda.Configuration { diff --git a/src/Dafda/Configuration/ProducerConfigurationBuilder.cs b/src/Dafda/Configuration/ProducerConfigurationBuilder.cs index e8c39ce1..f2ac63ff 100644 --- a/src/Dafda/Configuration/ProducerConfigurationBuilder.cs +++ b/src/Dafda/Configuration/ProducerConfigurationBuilder.cs @@ -3,129 +3,127 @@ using System.Linq; using Dafda.Producing; using Dafda.Serializing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Dafda.Configuration -{ - using Microsoft.Extensions.DependencyInjection; +namespace Dafda.Configuration; - internal sealed class ProducerConfigurationBuilder +internal sealed class ProducerConfigurationBuilder +{ + private static readonly string[] DefaultConfigurationKeys = { - private static readonly string[] DefaultConfigurationKeys = - { - ConfigurationKey.BootstrapServers, - ConfigurationKey.BrokerVersionFallback, - ConfigurationKey.ApiVersionFallbackMs, - ConfigurationKey.SslCaLocation, - ConfigurationKey.SaslUsername, - ConfigurationKey.SaslPassword, - ConfigurationKey.SaslMechanisms, - ConfigurationKey.SecurityProtocol, - }; - - private static readonly string[] RequiredConfigurationKeys = - { - ConfigurationKey.BootstrapServers - }; + ConfigurationKey.BootstrapServers, + ConfigurationKey.BrokerVersionFallback, + ConfigurationKey.ApiVersionFallbackMs, + ConfigurationKey.SslCaLocation, + ConfigurationKey.SaslUsername, + ConfigurationKey.SaslPassword, + ConfigurationKey.SaslMechanisms, + ConfigurationKey.SecurityProtocol, + }; + + private static readonly string[] RequiredConfigurationKeys = + { + ConfigurationKey.BootstrapServers + }; - private readonly IDictionary _configurations = new Dictionary(); - private readonly IList _namingConventions = new List(); + private readonly IDictionary _configurations = new Dictionary(); + private readonly IList _namingConventions = new List(); - private ConfigurationSource _configurationSource = ConfigurationSource.Null; - private MessageIdGenerator _messageIdGenerator = MessageIdGenerator.Default; - private Func _kafkaProducerFactory; - private static readonly IPayloadSerializer _defaultPayloadSerializer = new DefaultPayloadSerializer(); - private readonly TopicPayloadSerializerRegistry _topicPayloadSerializerRegistry = new TopicPayloadSerializerRegistry(() => _defaultPayloadSerializer); + private ConfigurationSource _configurationSource = ConfigurationSource.Null; + private MessageIdGenerator _messageIdGenerator = MessageIdGenerator.Default; + private Func _kafkaProducerFactory; + private static readonly IPayloadSerializer _defaultPayloadSerializer = new DefaultPayloadSerializer(); + private readonly TopicPayloadSerializerRegistry _topicPayloadSerializerRegistry = new TopicPayloadSerializerRegistry(() => _defaultPayloadSerializer); - public ProducerConfigurationBuilder WithConfigurationSource(ConfigurationSource configurationSource) - { - _configurationSource = configurationSource; - return this; - } - - public ProducerConfigurationBuilder WithNamingConvention(Func converter) - { - _namingConventions.Add(NamingConvention.UseCustom(converter)); - return this; - } - - internal ProducerConfigurationBuilder WithNamingConvention(NamingConvention namingConvention) - { - _namingConventions.Add(namingConvention); - return this; - } + public ProducerConfigurationBuilder WithConfigurationSource(ConfigurationSource configurationSource) + { + _configurationSource = configurationSource; + return this; + } - public ProducerConfigurationBuilder WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) - { - WithNamingConvention(NamingConvention.UseEnvironmentStyle(prefix)); + public ProducerConfigurationBuilder WithNamingConvention(Func converter) + { + _namingConventions.Add(NamingConvention.UseCustom(converter)); + return this; + } - foreach (var additionalPrefix in additionalPrefixes) - { - WithNamingConvention(NamingConvention.UseEnvironmentStyle(additionalPrefix)); - } + internal ProducerConfigurationBuilder WithNamingConvention(NamingConvention namingConvention) + { + _namingConventions.Add(namingConvention); + return this; + } - return this; - } + public ProducerConfigurationBuilder WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) + { + WithNamingConvention(NamingConvention.UseEnvironmentStyle(prefix)); - public ProducerConfigurationBuilder WithConfiguration(string key, string value) + foreach (var additionalPrefix in additionalPrefixes) { - _configurations[key] = value; - return this; + WithNamingConvention(NamingConvention.UseEnvironmentStyle(additionalPrefix)); } - public ProducerConfigurationBuilder WithBootstrapServers(string bootstrapServers) - { - return WithConfiguration(ConfigurationKey.BootstrapServers, bootstrapServers); - } + return this; + } - public ProducerConfigurationBuilder WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) - { - _messageIdGenerator = messageIdGenerator; - return this; - } + public ProducerConfigurationBuilder WithConfiguration(string key, string value) + { + _configurations[key] = value; + return this; + } - internal ProducerConfigurationBuilder WithKafkaProducerFactory(Func inlineFactory) - { - _kafkaProducerFactory = inlineFactory; - return this; - } + public ProducerConfigurationBuilder WithBootstrapServers(string bootstrapServers) + { + return WithConfiguration(ConfigurationKey.BootstrapServers, bootstrapServers); + } - public ProducerConfigurationBuilder WithDefaultPayloadSerializer(Func payloadSerializerFactory) - { - _topicPayloadSerializerRegistry.SetDefaultPayloadSerializer(payloadSerializerFactory); - return this; - } + public ProducerConfigurationBuilder WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) + { + _messageIdGenerator = messageIdGenerator; + return this; + } - public ProducerConfigurationBuilder WithPayloadSerializer(string topic, Func payloadSerializerFactory) - { - _topicPayloadSerializerRegistry.Register(topic, payloadSerializerFactory); - return this; - } + internal ProducerConfigurationBuilder WithKafkaProducerFactory(Func inlineFactory) + { + _kafkaProducerFactory = inlineFactory; + return this; + } + + public ProducerConfigurationBuilder WithDefaultPayloadSerializer(Func payloadSerializerFactory) + { + _topicPayloadSerializerRegistry.SetDefaultPayloadSerializer(payloadSerializerFactory); + return this; + } + + public ProducerConfigurationBuilder WithPayloadSerializer(string topic, Func payloadSerializerFactory) + { + _topicPayloadSerializerRegistry.Register(topic, payloadSerializerFactory); + return this; + } - internal ProducerConfiguration Build() + internal ProducerConfiguration Build() + { + var configurations = new ConfigurationBuilder() + .WithConfigurationKeys(DefaultConfigurationKeys) + .WithRequiredConfigurationKeys(RequiredConfigurationKeys) + .WithNamingConventions(_namingConventions.ToArray()) + .WithConfigurationSource(_configurationSource) + .WithConfigurations(_configurations) + .Build(); + + if (_kafkaProducerFactory == null) { - var configurations = new ConfigurationBuilder() - .WithConfigurationKeys(DefaultConfigurationKeys) - .WithRequiredConfigurationKeys(RequiredConfigurationKeys) - .WithNamingConventions(_namingConventions.ToArray()) - .WithConfigurationSource(_configurationSource) - .WithConfigurations(_configurations) - .Build(); - - if (_kafkaProducerFactory == null) + _kafkaProducerFactory = provider => { - _kafkaProducerFactory = provider => - { - var loggerFactory = provider.GetRequiredService(); - return new KafkaProducer(loggerFactory, configurations, _topicPayloadSerializerRegistry); - }; - } - - return new ProducerConfiguration( - configurations, - _messageIdGenerator, - _kafkaProducerFactory - ); + var loggerFactory = provider.GetRequiredService(); + return new KafkaProducer(loggerFactory, configurations, _topicPayloadSerializerRegistry); + }; } + + return new ProducerConfiguration( + configurations, + _messageIdGenerator, + _kafkaProducerFactory + ); } } \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerOptions.cs b/src/Dafda/Configuration/ProducerOptions.cs index 1638db3b..071aa92c 100644 --- a/src/Dafda/Configuration/ProducerOptions.cs +++ b/src/Dafda/Configuration/ProducerOptions.cs @@ -1,162 +1,160 @@ using System; using Dafda.Producing; using Dafda.Serializing; -using Microsoft.Extensions.Logging; -namespace Dafda.Configuration +namespace Dafda.Configuration; + +/// +/// Facilitates Dafda configuration in .NET applications using the . +/// +public sealed class ProducerOptions { + internal readonly ProducerConfigurationBuilder Builder = new(); + internal readonly OutgoingMessageRegistry OutgoingMessageRegistry = new(); + /// - /// Facilitates Dafda configuration in .NET applications using the . + /// Specify a custom implementation of the to use. /// - public sealed class ProducerOptions + /// The to use. + public void WithConfigurationSource(ConfigurationSource configurationSource) { - internal readonly ProducerConfigurationBuilder Builder = new(); - internal readonly OutgoingMessageRegistry OutgoingMessageRegistry = new(); - - /// - /// Specify a custom implementation of the to use. - /// - /// The to use. - public void WithConfigurationSource(ConfigurationSource configurationSource) - { - Builder.WithConfigurationSource(configurationSource); - } + Builder.WithConfigurationSource(configurationSource); + } - /// - /// Use as the configuration source. - /// - /// The configuration instance. - public void WithConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) - { - Builder.WithConfigurationSource(new DefaultConfigurationSource(configuration)); - } + /// + /// Use as the configuration source. + /// + /// The configuration instance. + public void WithConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) + { + Builder.WithConfigurationSource(new DefaultConfigurationSource(configuration)); + } - /// - /// Add a custom naming convention for converting configuration keys when - /// looking up keys in the . - /// - /// Use this to transform keys. - public void WithNamingConvention(Func converter) - { - Builder.WithNamingConvention(converter); - } + /// + /// Add a custom naming convention for converting configuration keys when + /// looking up keys in the . + /// + /// Use this to transform keys. + public void WithNamingConvention(Func converter) + { + Builder.WithNamingConvention(converter); + } - /// - /// Add default environment style naming convention. The configuration will attempt to - /// fetch keys from , using the following scheme: - /// - /// keys will be converted to uppercase. - /// any one or more of SPACE, TAB, ., and - will be converted to a single _. - /// the prefix will be prefixed (in uppercase) along with a _. - /// - /// - /// When configuring a consumer the WithEnvironmentStyle("app"), Dafda will attempt to find the - /// key APP_GROUP_ID in the . - /// - /// The prefix to use before keys. - /// Additional prefixes to use before keys. - public void WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) - { - Builder.WithEnvironmentStyle(prefix, additionalPrefixes); - } + /// + /// Add default environment style naming convention. The configuration will attempt to + /// fetch keys from , using the following scheme: + /// + /// keys will be converted to uppercase. + /// any one or more of SPACE, TAB, ., and - will be converted to a single _. + /// the prefix will be prefixed (in uppercase) along with a _. + /// + /// + /// When configuring a consumer the WithEnvironmentStyle("app"), Dafda will attempt to find the + /// key APP_GROUP_ID in the . + /// + /// The prefix to use before keys. + /// Additional prefixes to use before keys. + public void WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) + { + Builder.WithEnvironmentStyle(prefix, additionalPrefixes); + } - /// - /// Add a configuration key/value directly to the underlying Kafka consumer. - /// - /// The configuration key. - /// The configuration value. - public void WithConfiguration(string key, string value) - { - Builder.WithConfiguration(key, value); - } + /// + /// Add a configuration key/value directly to the underlying Kafka consumer. + /// + /// The configuration key. + /// The configuration value. + public void WithConfiguration(string key, string value) + { + Builder.WithConfiguration(key, value); + } - /// - /// A shorthand to set the bootstrap.servers Kafka configuration value. - /// - /// A list of bootstrap servers. - public void WithBootstrapServers(string bootstrapServers) - { - Builder.WithBootstrapServers(bootstrapServers); - } + /// + /// A shorthand to set the bootstrap.servers Kafka configuration value. + /// + /// A list of bootstrap servers. + public void WithBootstrapServers(string bootstrapServers) + { + Builder.WithBootstrapServers(bootstrapServers); + } - internal void WithKafkaProducerFactory(Func inlineFactory) - { - Builder.WithKafkaProducerFactory(inlineFactory); - } + internal void WithKafkaProducerFactory(Func inlineFactory) + { + Builder.WithKafkaProducerFactory(inlineFactory); + } - /// - /// Override the default Dafda implementation of . - /// - /// A custom implementation of . - public void WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) - { - Builder.WithMessageIdGenerator(messageIdGenerator); - } + /// + /// Override the default Dafda implementation of . + /// + /// A custom implementation of . + public void WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) + { + Builder.WithMessageIdGenerator(messageIdGenerator); + } - /// - /// Register outbox messages. - /// - /// The type of message going to the call. - /// The target topic. - /// The event type to use in the Dafda message envelope. - /// The key selector takes an instance of , - /// and returns a string of the Kafka partition key. - public void Register(string topic, string type, Func keySelector) where T : class - { - OutgoingMessageRegistry.Register(topic, type, keySelector); - } + /// + /// Register outbox messages. + /// + /// The type of message going to the call. + /// The target topic. + /// The event type to use in the Dafda message envelope. + /// The key selector takes an instance of , + /// and returns a string of the Kafka partition key. + public void Register(string topic, string type, Func keySelector) where T : class + { + OutgoingMessageRegistry.Register(topic, type, keySelector); + } - /// - /// Override the with a custom implementation - /// - /// A custom implementation of - public void WithDefaultPayloadSerializer(IPayloadSerializer payloadSerializer) - { - WithDefaultPayloadSerializer(() => payloadSerializer); - } + /// + /// Override the with a custom implementation + /// + /// A custom implementation of + public void WithDefaultPayloadSerializer(IPayloadSerializer payloadSerializer) + { + WithDefaultPayloadSerializer(() => payloadSerializer); + } - /// - /// Override the with a custom implementation - /// - /// A factory method that returns a custom implementation - /// of - /// - public void WithDefaultPayloadSerializer(Func payloadSerializerFactory) - { - Builder.WithDefaultPayloadSerializer(payloadSerializerFactory); - } + /// + /// Override the with a custom implementation + /// + /// A factory method that returns a custom implementation + /// of + /// + public void WithDefaultPayloadSerializer(Func payloadSerializerFactory) + { + Builder.WithDefaultPayloadSerializer(payloadSerializerFactory); + } - /// - /// Override the with a custom implementation for - /// the specified - /// - /// Name of the topic - /// A custom implementation of - public void WithPayloadSerializer(string topic, IPayloadSerializer payloadSerializer) - { - WithPayloadSerializer(topic, () => payloadSerializer); - } + /// + /// Override the with a custom implementation for + /// the specified + /// + /// Name of the topic + /// A custom implementation of + public void WithPayloadSerializer(string topic, IPayloadSerializer payloadSerializer) + { + WithPayloadSerializer(topic, () => payloadSerializer); + } - /// - /// Override the with a custom implementation for - /// the specified - /// - /// Name of the topic - /// A factory method that returns a custom implementation - /// of - /// - public void WithPayloadSerializer(string topic, Func payloadSerializerFactory) - { - Builder.WithPayloadSerializer(topic, payloadSerializerFactory); - } + /// + /// Override the with a custom implementation for + /// the specified + /// + /// Name of the topic + /// A factory method that returns a custom implementation + /// of + /// + public void WithPayloadSerializer(string topic, Func payloadSerializerFactory) + { + Builder.WithPayloadSerializer(topic, payloadSerializerFactory); + } - private class DefaultConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) - : ConfigurationSource + private class DefaultConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) + : ConfigurationSource + { + public override string GetByKey(string key) { - public override string GetByKey(string key) - { - return configuration[key]; - } + return configuration[key]; } } } \ No newline at end of file From 7beac1ceef73933f3db312947a7a542ee022980f Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Thu, 12 Mar 2026 14:18:53 +0100 Subject: [PATCH 11/24] sad --- .../TestServiceCollectionExtensions.cs | 483 +++++++++++++----- src/Dafda/Producing/Producer.cs | 12 +- 2 files changed, 353 insertions(+), 142 deletions(-) diff --git a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs index d251ab0e..18fb9314 100644 --- a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs +++ b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Dafda.Configuration; using Dafda.Producing; @@ -5,186 +6,396 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace Dafda.Tests.Configuration +namespace Dafda.Tests.Configuration; + +public class TestServiceCollectionExtensions { - public class TestServiceCollectionExtensions + [Fact] + public async Task Can_produce_message() { - [Fact] - public async Task Can_produce_message() + var spy = new KafkaProducerSpy(); + + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(options => { - var spy = new KafkaProducerSpy(); + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(_ => spy); + options.Register("foo", "bar", x => "baz"); + }); - var services = new ServiceCollection(); - services.AddLogging(); - services.AddProducerFor(options => - { - options.WithBootstrapServers("dummy"); - options.WithKafkaProducerFactory(_ => spy); - options.Register("foo", "bar", x => "baz"); - }); + var provider = services.BuildServiceProvider(); - var provider = services.BuildServiceProvider(); + var simpleSender = provider.GetRequiredService(); + var producer = simpleSender.Producer; - var simpleSender = provider.GetRequiredService(); - var producer = simpleSender.Producer; + await producer.Produce(new DummyMessage()); - await producer.Produce(new DummyMessage()); + Assert.Equal("foo", spy.Topic); + Assert.Equal("baz", spy.Key); + } - Assert.Equal("foo", spy.Topic); - Assert.Equal("baz", spy.Key); - } + [Fact] + public void registers_a_typed_producer() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddTransient(); - [Fact] - public void registers_a_typed_producer() + services.AddProducerFor(options => { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddTransient(); - - services.AddProducerFor(options => - { - options.WithBootstrapServers("dummy"); - }); + options.WithBootstrapServers("dummy"); + }); - var provider = services.BuildServiceProvider(); - var messageSender = provider.GetRequiredService(); + var provider = services.BuildServiceProvider(); + var messageSender = provider.GetRequiredService(); - Assert.NotNull(messageSender); - Assert.NotNull(messageSender.Producer); - Assert.NotNull(messageSender.ADependency); + Assert.NotNull(messageSender); + Assert.NotNull(messageSender.Producer); + Assert.NotNull(messageSender.ADependency); - Assert.Equal("hello one", messageSender.ADependency.Message); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); - } - - [Fact] - public void resolves_a_typed_producer_for_an_abstract_service() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddTransient(); - services.AddProducerFor(options => - { - options.WithBootstrapServers("dummy"); - }); - var provider = services.BuildServiceProvider(); - var messageSender = provider.GetRequiredService(); - - Assert.NotNull(messageSender); - Assert.NotNull(messageSender.Producer); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); - } + Assert.Equal("hello one", messageSender.ADependency.Message); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + } - [Fact] - public void registers_multiple_typed_producer() + [Fact] + public void resolves_a_typed_producer_for_an_abstract_service() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddTransient(); + services.AddProducerFor(options => { - var services = new ServiceCollection(); + options.WithBootstrapServers("dummy"); + }); + var provider = services.BuildServiceProvider(); + var messageSender = provider.GetRequiredService(); + + Assert.NotNull(messageSender); + Assert.NotNull(messageSender.Producer); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + } + + [Fact] + public void registers_multiple_typed_producer() + { + var services = new ServiceCollection(); - services.AddLogging(); - services.AddTransient(); - services.AddProducerFor(options => - { - options.WithBootstrapServers("dummy"); - }); - - services.AddTransient(); - services.AddProducerFor(options => - { - options.WithBootstrapServers("dummy"); - }); - - var provider = services.BuildServiceProvider(); + services.AddLogging(); + services.AddTransient(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + + services.AddTransient(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + + var provider = services.BuildServiceProvider(); - var messageSenderOne = provider.GetRequiredService(); + var messageSenderOne = provider.GetRequiredService(); - Assert.NotNull(messageSenderOne); - Assert.NotNull(messageSenderOne.Producer); - Assert.NotNull(messageSenderOne.ADependency); + Assert.NotNull(messageSenderOne); + Assert.NotNull(messageSenderOne.Producer); + Assert.NotNull(messageSenderOne.ADependency); - Assert.Equal("hello one", messageSenderOne.ADependency.Message); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSenderOne.Producer.Name); + Assert.Equal("hello one", messageSenderOne.ADependency.Message); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSenderOne.Producer.Name); - var messageSenderTwo = provider.GetRequiredService(); + var messageSenderTwo = provider.GetRequiredService(); - Assert.NotNull(messageSenderTwo); - Assert.NotNull(messageSenderTwo.Producer); - Assert.NotNull(messageSenderTwo.ADependency); + Assert.NotNull(messageSenderTwo); + Assert.NotNull(messageSenderTwo.Producer); + Assert.NotNull(messageSenderTwo.ADependency); - Assert.Equal("hello two", messageSenderTwo.ADependency.Message); - Assert.Equal(messageSenderTwo.Producer.Name, messageSenderTwo.Producer.Name); - } + Assert.Equal("hello two", messageSenderTwo.ADependency.Message); + Assert.Equal(messageSenderTwo.Producer.Name, messageSenderTwo.Producer.Name); + } - [Fact] - public void does_not_register_a_producer_directly() - { - var services = new ServiceCollection(); + [Fact] + public void does_not_register_a_producer_directly() + { + var services = new ServiceCollection(); - services.AddProducerFor(options => - { - options.WithBootstrapServers("dummy"); - }); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); - var provider = services.BuildServiceProvider(); - var producer = provider.GetService(); + var provider = services.BuildServiceProvider(); + var producer = provider.GetService(); - Assert.Null(producer); - } + Assert.Null(producer); + } - #region helper classes + [Fact] + public async Task Can_produce_message_with_service_provider_options_factory() + { + var spy = new KafkaProducerSpy(); - private class SimpleSender + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(_ => { - public SimpleSender(Producer producer) - { - Producer = producer; - } - - public Producer Producer { get; private set; } - } + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(__ => spy); + options.Register("foo", "bar", x => "baz"); + return options; + }); + + var provider = services.BuildServiceProvider(); + + var simpleSender = provider.GetRequiredService(); + var producer = simpleSender.Producer; + + await producer.Produce(new DummyMessage()); + + Assert.Equal("foo", spy.Topic); + Assert.Equal("baz", spy.Key); + } + + [Fact] + public void registers_a_typed_producer_with_service_provider_options_factory() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddTransient(); + + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + + var provider = services.BuildServiceProvider(); + var messageSender = provider.GetRequiredService(); + + Assert.NotNull(messageSender); + Assert.NotNull(messageSender.Producer); + Assert.NotNull(messageSender.ADependency); + + Assert.Equal("hello one", messageSender.ADependency.Message); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + } + + [Fact] + public void resolves_a_typed_producer_for_an_abstract_service_with_service_provider_options_factory() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddTransient(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + var provider = services.BuildServiceProvider(); + var messageSender = provider.GetRequiredService(); + + Assert.NotNull(messageSender); + Assert.NotNull(messageSender.Producer); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + } + + [Fact] + public void registers_multiple_typed_producers_with_service_provider_options_factory() + { + var services = new ServiceCollection(); + + services.AddLogging(); + services.AddTransient(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + + services.AddTransient(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + + var provider = services.BuildServiceProvider(); + + var messageSenderOne = provider.GetRequiredService(); + + Assert.NotNull(messageSenderOne); + Assert.NotNull(messageSenderOne.Producer); + Assert.NotNull(messageSenderOne.ADependency); + + Assert.Equal("hello one", messageSenderOne.ADependency.Message); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSenderOne.Producer.Name); - private interface IMessageSenderOne + var messageSenderTwo = provider.GetRequiredService(); + + Assert.NotNull(messageSenderTwo); + Assert.NotNull(messageSenderTwo.Producer); + Assert.NotNull(messageSenderTwo.ADependency); + + Assert.Equal("hello two", messageSenderTwo.ADependency.Message); + Assert.Equal(typeof(MessageSenderTwo).FullName, messageSenderTwo.Producer.Name); + } + + [Fact] + public void does_not_register_a_producer_directly_with_service_provider_options_factory() + { + var services = new ServiceCollection(); + + services.AddProducerFor(_ => { - public Producer Producer { get; } + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + + var provider = services.BuildServiceProvider(); + var producer = provider.GetService(); + + Assert.Null(producer); + } + + [Fact] + public void producer_factory_is_singleton_reusing_same_kafka_producer_instance() + { + var spy = new KafkaProducerSpy(); + + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(_ => spy); + }); + + var provider = services.BuildServiceProvider(); + + var senderOne = provider.GetRequiredService(); + var senderTwo = provider.GetRequiredService(); + + Assert.Same(senderOne.Producer.KafkaProducer, senderTwo.Producer.KafkaProducer); + } + + [Fact] + public void producer_factory_is_singleton_reusing_same_kafka_producer_instance_with_service_provider_options_factory() + { + var spy = new KafkaProducerSpy(); + + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(__ => spy); + return options; + }); + + var provider = services.BuildServiceProvider(); + + var senderOne = provider.GetRequiredService(); + var senderTwo = provider.GetRequiredService(); + + Assert.Same(senderOne.Producer.KafkaProducer, senderTwo.Producer.KafkaProducer); + } + + [Fact] + public void options_factory_receives_service_provider() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddSingleton(new BootstrapServerSettings("dummy")); + + IServiceProvider capturedProvider = null; + + services.AddProducerFor(sp => + { + capturedProvider = sp; + var settings = sp.GetRequiredService(); + var options = new ProducerOptions(); + options.WithBootstrapServers(settings.BootstrapServers); + return options; + }); + + var provider = services.BuildServiceProvider(); + _ = provider.GetRequiredService(); + + Assert.NotNull(capturedProvider); + } + + #region helper classes + + private class SimpleSender + { + public SimpleSender(Producer producer) + { + Producer = producer; } + + public Producer Producer { get; private set; } + } - private class MessageSenderOne : IMessageSenderOne + private interface IMessageSenderOne + { + public Producer Producer { get; } + } + + private class MessageSenderOne : IMessageSenderOne + { + public MessageSenderOne(Producer producer, AnotherDependency anotherDependency) { - public MessageSenderOne(Producer producer, AnotherDependency anotherDependency) - { - ADependency = anotherDependency; - Producer = producer; - } + ADependency = anotherDependency; + Producer = producer; + } - public Producer Producer { get; } - public AnotherDependency ADependency { get; } + public Producer Producer { get; } + public AnotherDependency ADependency { get; } - public class AnotherDependency - { - public string Message => "hello one"; - } + public class AnotherDependency + { + public string Message => "hello one"; } + } - private class MessageSenderTwo + private class MessageSenderTwo + { + public MessageSenderTwo(Producer producer, AnotherDependency anotherDependency) { - public MessageSenderTwo(Producer producer, AnotherDependency anotherDependency) - { - ADependency = anotherDependency; - Producer = producer; - } + ADependency = anotherDependency; + Producer = producer; + } - public Producer Producer { get; } - public AnotherDependency ADependency { get; } + public Producer Producer { get; } + public AnotherDependency ADependency { get; } - public class AnotherDependency - { - public string Message => "hello two"; - } + public class AnotherDependency + { + public string Message => "hello two"; } + } - #endregion - - public class DummyMessage + private class BootstrapServerSettings + { + public BootstrapServerSettings(string bootstrapServers) { + BootstrapServers = bootstrapServers; } + + public string BootstrapServers { get; } + } + + #endregion + + public class DummyMessage + { } } \ No newline at end of file diff --git a/src/Dafda/Producing/Producer.cs b/src/Dafda/Producing/Producer.cs index 44059544..571eb745 100644 --- a/src/Dafda/Producing/Producer.cs +++ b/src/Dafda/Producing/Producer.cs @@ -11,16 +11,16 @@ namespace Dafda.Producing /// public sealed class Producer { - private readonly KafkaProducer _kafkaProducer; private readonly PayloadDescriptorFactory _payloadDescriptorFactory; internal Producer(KafkaProducer kafkaProducer, OutgoingMessageRegistry outgoingMessageRegistry, MessageIdGenerator messageIdGenerator) { - _kafkaProducer = kafkaProducer; + KafkaProducer = kafkaProducer; _payloadDescriptorFactory = new PayloadDescriptorFactory(outgoingMessageRegistry, messageIdGenerator); } internal string Name { get; set; } = "__Default Producer__"; + internal KafkaProducer KafkaProducer { get; } /// /// Produce a on Kafka @@ -39,10 +39,10 @@ public async Task Produce(object message) public async Task Produce(object message, Metadata headers) { var payloadDescriptor = _payloadDescriptorFactory.Create(message, headers); - payloadDescriptor.ClientId = _kafkaProducer.ClientId; + payloadDescriptor.ClientId = KafkaProducer.ClientId; using var activity = DafdaActivitySource.StartPublishingActivity(payloadDescriptor); - await _kafkaProducer.Produce(payloadDescriptor); + await KafkaProducer.Produce(payloadDescriptor); } /// @@ -75,10 +75,10 @@ public async Task Produce(object message, MessageHandlerContext context) public async Task Produce(object message, MessageHandlerContext context, Dictionary headers) { var payloadDescriptor = _payloadDescriptorFactory.Create(message, context, headers); - payloadDescriptor.ClientId = _kafkaProducer.ClientId; + payloadDescriptor.ClientId = KafkaProducer.ClientId; using var activity = DafdaActivitySource.StartPublishingActivity(payloadDescriptor); - await _kafkaProducer.Produce(payloadDescriptor); + await KafkaProducer.Produce(payloadDescriptor); } } } \ No newline at end of file From d0bc2ea01d2166b9ac44c5d1a4846d31b146e094 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Mon, 16 Mar 2026 10:50:07 +0100 Subject: [PATCH 12/24] sd --- src/Dafda/Configuration/ConsumerConfigurationBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Dafda/Configuration/ConsumerConfigurationBase.cs b/src/Dafda/Configuration/ConsumerConfigurationBase.cs index ab005bc9..b1fd24c6 100644 --- a/src/Dafda/Configuration/ConsumerConfigurationBase.cs +++ b/src/Dafda/Configuration/ConsumerConfigurationBase.cs @@ -1,8 +1,8 @@ -using System; +using Dafda.Consuming.Interfaces; +using Dafda.Consuming; +using System; using System.Collections.Generic; using System.Text; -using Dafda.Consuming; -using Dafda.Consuming.Interfaces; namespace Dafda.Configuration { From f8dad6cd7ccf4b56d237363a33ff1883a9f01ab0 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 17 Mar 2026 08:24:30 +0100 Subject: [PATCH 13/24] revert namesapce styling change --- .../TestServiceCollectionExtensions.cs | 601 +++++++++--------- .../Configuration/ProducerConfiguration.cs | 23 +- .../ProducerConfigurationBuilder.cs | 199 +++--- src/Dafda/Configuration/ProducerOptions.cs | 271 ++++---- .../ProducerServiceCollectionExtensions.cs | 207 +++--- 5 files changed, 655 insertions(+), 646 deletions(-) diff --git a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs index 18fb9314..c71c87e7 100644 --- a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs +++ b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs @@ -6,396 +6,397 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace Dafda.Tests.Configuration; - -public class TestServiceCollectionExtensions +namespace Dafda.Tests.Configuration { - [Fact] - public async Task Can_produce_message() + public class TestServiceCollectionExtensions { - var spy = new KafkaProducerSpy(); - - var services = new ServiceCollection(); - services.AddLogging(); - services.AddProducerFor(options => + [Fact] + public async Task Can_produce_message() { - options.WithBootstrapServers("dummy"); - options.WithKafkaProducerFactory(_ => spy); - options.Register("foo", "bar", x => "baz"); - }); + var spy = new KafkaProducerSpy(); - var provider = services.BuildServiceProvider(); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(_ => spy); + options.Register("foo", "bar", x => "baz"); + }); - var simpleSender = provider.GetRequiredService(); - var producer = simpleSender.Producer; + var provider = services.BuildServiceProvider(); - await producer.Produce(new DummyMessage()); + var simpleSender = provider.GetRequiredService(); + var producer = simpleSender.Producer; - Assert.Equal("foo", spy.Topic); - Assert.Equal("baz", spy.Key); - } + await producer.Produce(new DummyMessage()); - [Fact] - public void registers_a_typed_producer() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddTransient(); + Assert.Equal("foo", spy.Topic); + Assert.Equal("baz", spy.Key); + } - services.AddProducerFor(options => + [Fact] + public void registers_a_typed_producer() { - options.WithBootstrapServers("dummy"); - }); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddTransient(); - var provider = services.BuildServiceProvider(); - var messageSender = provider.GetRequiredService(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + + var provider = services.BuildServiceProvider(); + var messageSender = provider.GetRequiredService(); - Assert.NotNull(messageSender); - Assert.NotNull(messageSender.Producer); - Assert.NotNull(messageSender.ADependency); + Assert.NotNull(messageSender); + Assert.NotNull(messageSender.Producer); + Assert.NotNull(messageSender.ADependency); - Assert.Equal("hello one", messageSender.ADependency.Message); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); - } - - [Fact] - public void resolves_a_typed_producer_for_an_abstract_service() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddTransient(); - services.AddProducerFor(options => - { - options.WithBootstrapServers("dummy"); - }); - var provider = services.BuildServiceProvider(); - var messageSender = provider.GetRequiredService(); - - Assert.NotNull(messageSender); - Assert.NotNull(messageSender.Producer); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); - } + Assert.Equal("hello one", messageSender.ADependency.Message); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + } - [Fact] - public void registers_multiple_typed_producer() - { - var services = new ServiceCollection(); - - services.AddLogging(); - services.AddTransient(); - services.AddProducerFor(options => + [Fact] + public void resolves_a_typed_producer_for_an_abstract_service() { - options.WithBootstrapServers("dummy"); - }); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddTransient(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + var provider = services.BuildServiceProvider(); + var messageSender = provider.GetRequiredService(); + + Assert.NotNull(messageSender); + Assert.NotNull(messageSender.Producer); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + } - services.AddTransient(); - services.AddProducerFor(options => + [Fact] + public void registers_multiple_typed_producer() { - options.WithBootstrapServers("dummy"); - }); - - var provider = services.BuildServiceProvider(); + var services = new ServiceCollection(); + + services.AddLogging(); + services.AddTransient(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + + services.AddTransient(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + + var provider = services.BuildServiceProvider(); - var messageSenderOne = provider.GetRequiredService(); + var messageSenderOne = provider.GetRequiredService(); - Assert.NotNull(messageSenderOne); - Assert.NotNull(messageSenderOne.Producer); - Assert.NotNull(messageSenderOne.ADependency); + Assert.NotNull(messageSenderOne); + Assert.NotNull(messageSenderOne.Producer); + Assert.NotNull(messageSenderOne.ADependency); - Assert.Equal("hello one", messageSenderOne.ADependency.Message); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSenderOne.Producer.Name); + Assert.Equal("hello one", messageSenderOne.ADependency.Message); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSenderOne.Producer.Name); - var messageSenderTwo = provider.GetRequiredService(); + var messageSenderTwo = provider.GetRequiredService(); - Assert.NotNull(messageSenderTwo); - Assert.NotNull(messageSenderTwo.Producer); - Assert.NotNull(messageSenderTwo.ADependency); + Assert.NotNull(messageSenderTwo); + Assert.NotNull(messageSenderTwo.Producer); + Assert.NotNull(messageSenderTwo.ADependency); - Assert.Equal("hello two", messageSenderTwo.ADependency.Message); - Assert.Equal(messageSenderTwo.Producer.Name, messageSenderTwo.Producer.Name); - } + Assert.Equal("hello two", messageSenderTwo.ADependency.Message); + Assert.Equal(messageSenderTwo.Producer.Name, messageSenderTwo.Producer.Name); + } - [Fact] - public void does_not_register_a_producer_directly() - { - var services = new ServiceCollection(); - - services.AddProducerFor(options => + [Fact] + public void does_not_register_a_producer_directly() { - options.WithBootstrapServers("dummy"); - }); + var services = new ServiceCollection(); + + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); - var provider = services.BuildServiceProvider(); - var producer = provider.GetService(); + var provider = services.BuildServiceProvider(); + var producer = provider.GetService(); - Assert.Null(producer); - } - - [Fact] - public async Task Can_produce_message_with_service_provider_options_factory() - { - var spy = new KafkaProducerSpy(); + Assert.Null(producer); + } - var services = new ServiceCollection(); - services.AddLogging(); - services.AddProducerFor(_ => + [Fact] + public async Task Can_produce_message_with_service_provider_options_factory() { - var options = new ProducerOptions(); - options.WithBootstrapServers("dummy"); - options.WithKafkaProducerFactory(__ => spy); - options.Register("foo", "bar", x => "baz"); - return options; - }); + var spy = new KafkaProducerSpy(); - var provider = services.BuildServiceProvider(); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(__ => spy); + options.Register("foo", "bar", x => "baz"); + return options; + }); - var simpleSender = provider.GetRequiredService(); - var producer = simpleSender.Producer; + var provider = services.BuildServiceProvider(); - await producer.Produce(new DummyMessage()); + var simpleSender = provider.GetRequiredService(); + var producer = simpleSender.Producer; - Assert.Equal("foo", spy.Topic); - Assert.Equal("baz", spy.Key); - } + await producer.Produce(new DummyMessage()); - [Fact] - public void registers_a_typed_producer_with_service_provider_options_factory() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddTransient(); + Assert.Equal("foo", spy.Topic); + Assert.Equal("baz", spy.Key); + } - services.AddProducerFor(_ => + [Fact] + public void registers_a_typed_producer_with_service_provider_options_factory() { - var options = new ProducerOptions(); - options.WithBootstrapServers("dummy"); - return options; - }); - - var provider = services.BuildServiceProvider(); - var messageSender = provider.GetRequiredService(); - - Assert.NotNull(messageSender); - Assert.NotNull(messageSender.Producer); - Assert.NotNull(messageSender.ADependency); - - Assert.Equal("hello one", messageSender.ADependency.Message); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); - } + var services = new ServiceCollection(); + services.AddLogging(); + services.AddTransient(); + + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + + var provider = services.BuildServiceProvider(); + var messageSender = provider.GetRequiredService(); + + Assert.NotNull(messageSender); + Assert.NotNull(messageSender.Producer); + Assert.NotNull(messageSender.ADependency); + + Assert.Equal("hello one", messageSender.ADependency.Message); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + } - [Fact] - public void resolves_a_typed_producer_for_an_abstract_service_with_service_provider_options_factory() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddTransient(); - services.AddProducerFor(_ => + [Fact] + public void resolves_a_typed_producer_for_an_abstract_service_with_service_provider_options_factory() { - var options = new ProducerOptions(); - options.WithBootstrapServers("dummy"); - return options; - }); - var provider = services.BuildServiceProvider(); - var messageSender = provider.GetRequiredService(); - - Assert.NotNull(messageSender); - Assert.NotNull(messageSender.Producer); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); - } - - [Fact] - public void registers_multiple_typed_producers_with_service_provider_options_factory() - { - var services = new ServiceCollection(); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddTransient(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + var provider = services.BuildServiceProvider(); + var messageSender = provider.GetRequiredService(); + + Assert.NotNull(messageSender); + Assert.NotNull(messageSender.Producer); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + } - services.AddLogging(); - services.AddTransient(); - services.AddProducerFor(_ => + [Fact] + public void registers_multiple_typed_producers_with_service_provider_options_factory() { - var options = new ProducerOptions(); - options.WithBootstrapServers("dummy"); - return options; - }); + var services = new ServiceCollection(); - services.AddTransient(); - services.AddProducerFor(_ => - { - var options = new ProducerOptions(); - options.WithBootstrapServers("dummy"); - return options; - }); + services.AddLogging(); + services.AddTransient(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); - var provider = services.BuildServiceProvider(); + services.AddTransient(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); - var messageSenderOne = provider.GetRequiredService(); + var provider = services.BuildServiceProvider(); - Assert.NotNull(messageSenderOne); - Assert.NotNull(messageSenderOne.Producer); - Assert.NotNull(messageSenderOne.ADependency); + var messageSenderOne = provider.GetRequiredService(); - Assert.Equal("hello one", messageSenderOne.ADependency.Message); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSenderOne.Producer.Name); + Assert.NotNull(messageSenderOne); + Assert.NotNull(messageSenderOne.Producer); + Assert.NotNull(messageSenderOne.ADependency); - var messageSenderTwo = provider.GetRequiredService(); + Assert.Equal("hello one", messageSenderOne.ADependency.Message); + Assert.Equal(typeof(MessageSenderOne).FullName, messageSenderOne.Producer.Name); - Assert.NotNull(messageSenderTwo); - Assert.NotNull(messageSenderTwo.Producer); - Assert.NotNull(messageSenderTwo.ADependency); + var messageSenderTwo = provider.GetRequiredService(); - Assert.Equal("hello two", messageSenderTwo.ADependency.Message); - Assert.Equal(typeof(MessageSenderTwo).FullName, messageSenderTwo.Producer.Name); - } + Assert.NotNull(messageSenderTwo); + Assert.NotNull(messageSenderTwo.Producer); + Assert.NotNull(messageSenderTwo.ADependency); - [Fact] - public void does_not_register_a_producer_directly_with_service_provider_options_factory() - { - var services = new ServiceCollection(); + Assert.Equal("hello two", messageSenderTwo.ADependency.Message); + Assert.Equal(typeof(MessageSenderTwo).FullName, messageSenderTwo.Producer.Name); + } - services.AddProducerFor(_ => + [Fact] + public void does_not_register_a_producer_directly_with_service_provider_options_factory() { - var options = new ProducerOptions(); - options.WithBootstrapServers("dummy"); - return options; - }); + var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var producer = provider.GetService(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); - Assert.Null(producer); - } + var provider = services.BuildServiceProvider(); + var producer = provider.GetService(); - [Fact] - public void producer_factory_is_singleton_reusing_same_kafka_producer_instance() - { - var spy = new KafkaProducerSpy(); + Assert.Null(producer); + } - var services = new ServiceCollection(); - services.AddLogging(); - services.AddProducerFor(options => + [Fact] + public void producer_factory_is_singleton_reusing_same_kafka_producer_instance() { - options.WithBootstrapServers("dummy"); - options.WithKafkaProducerFactory(_ => spy); - }); + var spy = new KafkaProducerSpy(); - var provider = services.BuildServiceProvider(); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(_ => spy); + }); - var senderOne = provider.GetRequiredService(); - var senderTwo = provider.GetRequiredService(); + var provider = services.BuildServiceProvider(); - Assert.Same(senderOne.Producer.KafkaProducer, senderTwo.Producer.KafkaProducer); - } + var senderOne = provider.GetRequiredService(); + var senderTwo = provider.GetRequiredService(); - [Fact] - public void producer_factory_is_singleton_reusing_same_kafka_producer_instance_with_service_provider_options_factory() - { - var spy = new KafkaProducerSpy(); + Assert.Same(senderOne.Producer.KafkaProducer, senderTwo.Producer.KafkaProducer); + } - var services = new ServiceCollection(); - services.AddLogging(); - services.AddProducerFor(_ => + [Fact] + public void producer_factory_is_singleton_reusing_same_kafka_producer_instance_with_service_provider_options_factory() { - var options = new ProducerOptions(); - options.WithBootstrapServers("dummy"); - options.WithKafkaProducerFactory(__ => spy); - return options; - }); + var spy = new KafkaProducerSpy(); - var provider = services.BuildServiceProvider(); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(__ => spy); + return options; + }); - var senderOne = provider.GetRequiredService(); - var senderTwo = provider.GetRequiredService(); + var provider = services.BuildServiceProvider(); - Assert.Same(senderOne.Producer.KafkaProducer, senderTwo.Producer.KafkaProducer); - } - - [Fact] - public void options_factory_receives_service_provider() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddSingleton(new BootstrapServerSettings("dummy")); + var senderOne = provider.GetRequiredService(); + var senderTwo = provider.GetRequiredService(); - IServiceProvider capturedProvider = null; + Assert.Same(senderOne.Producer.KafkaProducer, senderTwo.Producer.KafkaProducer); + } - services.AddProducerFor(sp => + [Fact] + public void options_factory_receives_service_provider() { - capturedProvider = sp; - var settings = sp.GetRequiredService(); - var options = new ProducerOptions(); - options.WithBootstrapServers(settings.BootstrapServers); - return options; - }); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddSingleton(new BootstrapServerSettings("dummy")); - var provider = services.BuildServiceProvider(); - _ = provider.GetRequiredService(); + IServiceProvider capturedProvider = null; - Assert.NotNull(capturedProvider); - } + services.AddProducerFor(sp => + { + capturedProvider = sp; + var settings = sp.GetRequiredService(); + var options = new ProducerOptions(); + options.WithBootstrapServers(settings.BootstrapServers); + return options; + }); - #region helper classes + var provider = services.BuildServiceProvider(); + _ = provider.GetRequiredService(); - private class SimpleSender - { - public SimpleSender(Producer producer) - { - Producer = producer; + Assert.NotNull(capturedProvider); } - - public Producer Producer { get; private set; } - } - private interface IMessageSenderOne - { - public Producer Producer { get; } - } + #region helper classes - private class MessageSenderOne : IMessageSenderOne - { - public MessageSenderOne(Producer producer, AnotherDependency anotherDependency) + private class SimpleSender { - ADependency = anotherDependency; - Producer = producer; - } + public SimpleSender(Producer producer) + { + Producer = producer; + } - public Producer Producer { get; } - public AnotherDependency ADependency { get; } + public Producer Producer { get; private set; } + } - public class AnotherDependency + private interface IMessageSenderOne { - public string Message => "hello one"; + public Producer Producer { get; } } - } - private class MessageSenderTwo - { - public MessageSenderTwo(Producer producer, AnotherDependency anotherDependency) + private class MessageSenderOne : IMessageSenderOne { - ADependency = anotherDependency; - Producer = producer; - } + public MessageSenderOne(Producer producer, AnotherDependency anotherDependency) + { + ADependency = anotherDependency; + Producer = producer; + } - public Producer Producer { get; } - public AnotherDependency ADependency { get; } + public Producer Producer { get; } + public AnotherDependency ADependency { get; } - public class AnotherDependency - { - public string Message => "hello two"; + public class AnotherDependency + { + public string Message => "hello one"; + } } - } - private class BootstrapServerSettings - { - public BootstrapServerSettings(string bootstrapServers) + private class MessageSenderTwo { - BootstrapServers = bootstrapServers; + public MessageSenderTwo(Producer producer, AnotherDependency anotherDependency) + { + ADependency = anotherDependency; + Producer = producer; + } + + public Producer Producer { get; } + public AnotherDependency ADependency { get; } + + public class AnotherDependency + { + public string Message => "hello two"; + } } - public string BootstrapServers { get; } - } + private class BootstrapServerSettings + { + public BootstrapServerSettings(string bootstrapServers) + { + BootstrapServers = bootstrapServers; + } - #endregion + public string BootstrapServers { get; } + } - public class DummyMessage - { + #endregion + + public class DummyMessage + { + } } } \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerConfiguration.cs b/src/Dafda/Configuration/ProducerConfiguration.cs index af961572..ddde970e 100644 --- a/src/Dafda/Configuration/ProducerConfiguration.cs +++ b/src/Dafda/Configuration/ProducerConfiguration.cs @@ -3,14 +3,19 @@ using Dafda.Producing; using Microsoft.Extensions.Logging; -namespace Dafda.Configuration; - -internal class ProducerConfiguration( - IDictionary configuration, - MessageIdGenerator messageIdGenerator, - Func kafkaProducerFactory) +namespace Dafda.Configuration { - public IDictionary KafkaConfiguration { get; } = configuration; - public MessageIdGenerator MessageIdGenerator { get; } = messageIdGenerator; - public Func KafkaProducerFactory { get; } = kafkaProducerFactory; + internal class ProducerConfiguration + { + public ProducerConfiguration(IDictionary configuration, MessageIdGenerator messageIdGenerator, Func kafkaProducerFactory) + { + KafkaConfiguration = configuration; + MessageIdGenerator = messageIdGenerator; + KafkaProducerFactory = kafkaProducerFactory; + } + + public IDictionary KafkaConfiguration { get; } + public MessageIdGenerator MessageIdGenerator { get; } + public Func KafkaProducerFactory { get; } + } } \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerConfigurationBuilder.cs b/src/Dafda/Configuration/ProducerConfigurationBuilder.cs index f2ac63ff..0fc72b0b 100644 --- a/src/Dafda/Configuration/ProducerConfigurationBuilder.cs +++ b/src/Dafda/Configuration/ProducerConfigurationBuilder.cs @@ -6,124 +6,125 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Dafda.Configuration; - -internal sealed class ProducerConfigurationBuilder +namespace Dafda.Configuration { - private static readonly string[] DefaultConfigurationKeys = - { - ConfigurationKey.BootstrapServers, - ConfigurationKey.BrokerVersionFallback, - ConfigurationKey.ApiVersionFallbackMs, - ConfigurationKey.SslCaLocation, - ConfigurationKey.SaslUsername, - ConfigurationKey.SaslPassword, - ConfigurationKey.SaslMechanisms, - ConfigurationKey.SecurityProtocol, - }; - - private static readonly string[] RequiredConfigurationKeys = + internal sealed class ProducerConfigurationBuilder { - ConfigurationKey.BootstrapServers - }; - - private readonly IDictionary _configurations = new Dictionary(); - private readonly IList _namingConventions = new List(); + private static readonly string[] DefaultConfigurationKeys = + { + ConfigurationKey.BootstrapServers, + ConfigurationKey.BrokerVersionFallback, + ConfigurationKey.ApiVersionFallbackMs, + ConfigurationKey.SslCaLocation, + ConfigurationKey.SaslUsername, + ConfigurationKey.SaslPassword, + ConfigurationKey.SaslMechanisms, + ConfigurationKey.SecurityProtocol, + }; + + private static readonly string[] RequiredConfigurationKeys = + { + ConfigurationKey.BootstrapServers + }; - private ConfigurationSource _configurationSource = ConfigurationSource.Null; - private MessageIdGenerator _messageIdGenerator = MessageIdGenerator.Default; - private Func _kafkaProducerFactory; - private static readonly IPayloadSerializer _defaultPayloadSerializer = new DefaultPayloadSerializer(); - private readonly TopicPayloadSerializerRegistry _topicPayloadSerializerRegistry = new TopicPayloadSerializerRegistry(() => _defaultPayloadSerializer); + private readonly IDictionary _configurations = new Dictionary(); + private readonly IList _namingConventions = new List(); - public ProducerConfigurationBuilder WithConfigurationSource(ConfigurationSource configurationSource) - { - _configurationSource = configurationSource; - return this; - } + private ConfigurationSource _configurationSource = ConfigurationSource.Null; + private MessageIdGenerator _messageIdGenerator = MessageIdGenerator.Default; + private Func _kafkaProducerFactory; + private static readonly IPayloadSerializer _defaultPayloadSerializer = new DefaultPayloadSerializer(); + private readonly TopicPayloadSerializerRegistry _topicPayloadSerializerRegistry = new TopicPayloadSerializerRegistry(() => _defaultPayloadSerializer); - public ProducerConfigurationBuilder WithNamingConvention(Func converter) - { - _namingConventions.Add(NamingConvention.UseCustom(converter)); - return this; - } - - internal ProducerConfigurationBuilder WithNamingConvention(NamingConvention namingConvention) - { - _namingConventions.Add(namingConvention); - return this; - } + public ProducerConfigurationBuilder WithConfigurationSource(ConfigurationSource configurationSource) + { + _configurationSource = configurationSource; + return this; + } - public ProducerConfigurationBuilder WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) - { - WithNamingConvention(NamingConvention.UseEnvironmentStyle(prefix)); + public ProducerConfigurationBuilder WithNamingConvention(Func converter) + { + _namingConventions.Add(NamingConvention.UseCustom(converter)); + return this; + } - foreach (var additionalPrefix in additionalPrefixes) + internal ProducerConfigurationBuilder WithNamingConvention(NamingConvention namingConvention) { - WithNamingConvention(NamingConvention.UseEnvironmentStyle(additionalPrefix)); + _namingConventions.Add(namingConvention); + return this; } - return this; - } + public ProducerConfigurationBuilder WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) + { + WithNamingConvention(NamingConvention.UseEnvironmentStyle(prefix)); - public ProducerConfigurationBuilder WithConfiguration(string key, string value) - { - _configurations[key] = value; - return this; - } + foreach (var additionalPrefix in additionalPrefixes) + { + WithNamingConvention(NamingConvention.UseEnvironmentStyle(additionalPrefix)); + } - public ProducerConfigurationBuilder WithBootstrapServers(string bootstrapServers) - { - return WithConfiguration(ConfigurationKey.BootstrapServers, bootstrapServers); - } + return this; + } - public ProducerConfigurationBuilder WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) - { - _messageIdGenerator = messageIdGenerator; - return this; - } + public ProducerConfigurationBuilder WithConfiguration(string key, string value) + { + _configurations[key] = value; + return this; + } - internal ProducerConfigurationBuilder WithKafkaProducerFactory(Func inlineFactory) - { - _kafkaProducerFactory = inlineFactory; - return this; - } + public ProducerConfigurationBuilder WithBootstrapServers(string bootstrapServers) + { + return WithConfiguration(ConfigurationKey.BootstrapServers, bootstrapServers); + } - public ProducerConfigurationBuilder WithDefaultPayloadSerializer(Func payloadSerializerFactory) - { - _topicPayloadSerializerRegistry.SetDefaultPayloadSerializer(payloadSerializerFactory); - return this; - } + public ProducerConfigurationBuilder WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) + { + _messageIdGenerator = messageIdGenerator; + return this; + } - public ProducerConfigurationBuilder WithPayloadSerializer(string topic, Func payloadSerializerFactory) - { - _topicPayloadSerializerRegistry.Register(topic, payloadSerializerFactory); - return this; - } + internal ProducerConfigurationBuilder WithKafkaProducerFactory(Func inlineFactory) + { + _kafkaProducerFactory = inlineFactory; + return this; + } + + public ProducerConfigurationBuilder WithDefaultPayloadSerializer(Func payloadSerializerFactory) + { + _topicPayloadSerializerRegistry.SetDefaultPayloadSerializer(payloadSerializerFactory); + return this; + } + + public ProducerConfigurationBuilder WithPayloadSerializer(string topic, Func payloadSerializerFactory) + { + _topicPayloadSerializerRegistry.Register(topic, payloadSerializerFactory); + return this; + } - internal ProducerConfiguration Build() - { - var configurations = new ConfigurationBuilder() - .WithConfigurationKeys(DefaultConfigurationKeys) - .WithRequiredConfigurationKeys(RequiredConfigurationKeys) - .WithNamingConventions(_namingConventions.ToArray()) - .WithConfigurationSource(_configurationSource) - .WithConfigurations(_configurations) - .Build(); - - if (_kafkaProducerFactory == null) + internal ProducerConfiguration Build() { - _kafkaProducerFactory = provider => + var configurations = new ConfigurationBuilder() + .WithConfigurationKeys(DefaultConfigurationKeys) + .WithRequiredConfigurationKeys(RequiredConfigurationKeys) + .WithNamingConventions(_namingConventions.ToArray()) + .WithConfigurationSource(_configurationSource) + .WithConfigurations(_configurations) + .Build(); + + if (_kafkaProducerFactory == null) { - var loggerFactory = provider.GetRequiredService(); - return new KafkaProducer(loggerFactory, configurations, _topicPayloadSerializerRegistry); - }; + _kafkaProducerFactory = provider => + { + var loggerFactory = provider.GetRequiredService(); + return new KafkaProducer(loggerFactory, configurations, _topicPayloadSerializerRegistry); + }; + } + + return new ProducerConfiguration( + configurations, + _messageIdGenerator, + _kafkaProducerFactory + ); } - - return new ProducerConfiguration( - configurations, - _messageIdGenerator, - _kafkaProducerFactory - ); } } \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerOptions.cs b/src/Dafda/Configuration/ProducerOptions.cs index 071aa92c..1393a71c 100644 --- a/src/Dafda/Configuration/ProducerOptions.cs +++ b/src/Dafda/Configuration/ProducerOptions.cs @@ -2,159 +2,160 @@ using Dafda.Producing; using Dafda.Serializing; -namespace Dafda.Configuration; - -/// -/// Facilitates Dafda configuration in .NET applications using the . -/// -public sealed class ProducerOptions +namespace Dafda.Configuration { - internal readonly ProducerConfigurationBuilder Builder = new(); - internal readonly OutgoingMessageRegistry OutgoingMessageRegistry = new(); - /// - /// Specify a custom implementation of the to use. + /// Facilitates Dafda configuration in .NET applications using the . /// - /// The to use. - public void WithConfigurationSource(ConfigurationSource configurationSource) + public sealed class ProducerOptions { - Builder.WithConfigurationSource(configurationSource); - } + internal readonly ProducerConfigurationBuilder Builder = new(); + internal readonly OutgoingMessageRegistry OutgoingMessageRegistry = new(); + + /// + /// Specify a custom implementation of the to use. + /// + /// The to use. + public void WithConfigurationSource(ConfigurationSource configurationSource) + { + Builder.WithConfigurationSource(configurationSource); + } - /// - /// Use as the configuration source. - /// - /// The configuration instance. - public void WithConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) - { - Builder.WithConfigurationSource(new DefaultConfigurationSource(configuration)); - } + /// + /// Use as the configuration source. + /// + /// The configuration instance. + public void WithConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) + { + Builder.WithConfigurationSource(new DefaultConfigurationSource(configuration)); + } - /// - /// Add a custom naming convention for converting configuration keys when - /// looking up keys in the . - /// - /// Use this to transform keys. - public void WithNamingConvention(Func converter) - { - Builder.WithNamingConvention(converter); - } + /// + /// Add a custom naming convention for converting configuration keys when + /// looking up keys in the . + /// + /// Use this to transform keys. + public void WithNamingConvention(Func converter) + { + Builder.WithNamingConvention(converter); + } - /// - /// Add default environment style naming convention. The configuration will attempt to - /// fetch keys from , using the following scheme: - /// - /// keys will be converted to uppercase. - /// any one or more of SPACE, TAB, ., and - will be converted to a single _. - /// the prefix will be prefixed (in uppercase) along with a _. - /// - /// - /// When configuring a consumer the WithEnvironmentStyle("app"), Dafda will attempt to find the - /// key APP_GROUP_ID in the . - /// - /// The prefix to use before keys. - /// Additional prefixes to use before keys. - public void WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) - { - Builder.WithEnvironmentStyle(prefix, additionalPrefixes); - } + /// + /// Add default environment style naming convention. The configuration will attempt to + /// fetch keys from , using the following scheme: + /// + /// keys will be converted to uppercase. + /// any one or more of SPACE, TAB, ., and - will be converted to a single _. + /// the prefix will be prefixed (in uppercase) along with a _. + /// + /// + /// When configuring a consumer the WithEnvironmentStyle("app"), Dafda will attempt to find the + /// key APP_GROUP_ID in the . + /// + /// The prefix to use before keys. + /// Additional prefixes to use before keys. + public void WithEnvironmentStyle(string prefix = null, params string[] additionalPrefixes) + { + Builder.WithEnvironmentStyle(prefix, additionalPrefixes); + } - /// - /// Add a configuration key/value directly to the underlying Kafka consumer. - /// - /// The configuration key. - /// The configuration value. - public void WithConfiguration(string key, string value) - { - Builder.WithConfiguration(key, value); - } + /// + /// Add a configuration key/value directly to the underlying Kafka consumer. + /// + /// The configuration key. + /// The configuration value. + public void WithConfiguration(string key, string value) + { + Builder.WithConfiguration(key, value); + } - /// - /// A shorthand to set the bootstrap.servers Kafka configuration value. - /// - /// A list of bootstrap servers. - public void WithBootstrapServers(string bootstrapServers) - { - Builder.WithBootstrapServers(bootstrapServers); - } + /// + /// A shorthand to set the bootstrap.servers Kafka configuration value. + /// + /// A list of bootstrap servers. + public void WithBootstrapServers(string bootstrapServers) + { + Builder.WithBootstrapServers(bootstrapServers); + } - internal void WithKafkaProducerFactory(Func inlineFactory) - { - Builder.WithKafkaProducerFactory(inlineFactory); - } + internal void WithKafkaProducerFactory(Func inlineFactory) + { + Builder.WithKafkaProducerFactory(inlineFactory); + } - /// - /// Override the default Dafda implementation of . - /// - /// A custom implementation of . - public void WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) - { - Builder.WithMessageIdGenerator(messageIdGenerator); - } + /// + /// Override the default Dafda implementation of . + /// + /// A custom implementation of . + public void WithMessageIdGenerator(MessageIdGenerator messageIdGenerator) + { + Builder.WithMessageIdGenerator(messageIdGenerator); + } - /// - /// Register outbox messages. - /// - /// The type of message going to the call. - /// The target topic. - /// The event type to use in the Dafda message envelope. - /// The key selector takes an instance of , - /// and returns a string of the Kafka partition key. - public void Register(string topic, string type, Func keySelector) where T : class - { - OutgoingMessageRegistry.Register(topic, type, keySelector); - } + /// + /// Register outbox messages. + /// + /// The type of message going to the call. + /// The target topic. + /// The event type to use in the Dafda message envelope. + /// The key selector takes an instance of , + /// and returns a string of the Kafka partition key. + public void Register(string topic, string type, Func keySelector) where T : class + { + OutgoingMessageRegistry.Register(topic, type, keySelector); + } - /// - /// Override the with a custom implementation - /// - /// A custom implementation of - public void WithDefaultPayloadSerializer(IPayloadSerializer payloadSerializer) - { - WithDefaultPayloadSerializer(() => payloadSerializer); - } + /// + /// Override the with a custom implementation + /// + /// A custom implementation of + public void WithDefaultPayloadSerializer(IPayloadSerializer payloadSerializer) + { + WithDefaultPayloadSerializer(() => payloadSerializer); + } - /// - /// Override the with a custom implementation - /// - /// A factory method that returns a custom implementation - /// of - /// - public void WithDefaultPayloadSerializer(Func payloadSerializerFactory) - { - Builder.WithDefaultPayloadSerializer(payloadSerializerFactory); - } + /// + /// Override the with a custom implementation + /// + /// A factory method that returns a custom implementation + /// of + /// + public void WithDefaultPayloadSerializer(Func payloadSerializerFactory) + { + Builder.WithDefaultPayloadSerializer(payloadSerializerFactory); + } - /// - /// Override the with a custom implementation for - /// the specified - /// - /// Name of the topic - /// A custom implementation of - public void WithPayloadSerializer(string topic, IPayloadSerializer payloadSerializer) - { - WithPayloadSerializer(topic, () => payloadSerializer); - } + /// + /// Override the with a custom implementation for + /// the specified + /// + /// Name of the topic + /// A custom implementation of + public void WithPayloadSerializer(string topic, IPayloadSerializer payloadSerializer) + { + WithPayloadSerializer(topic, () => payloadSerializer); + } - /// - /// Override the with a custom implementation for - /// the specified - /// - /// Name of the topic - /// A factory method that returns a custom implementation - /// of - /// - public void WithPayloadSerializer(string topic, Func payloadSerializerFactory) - { - Builder.WithPayloadSerializer(topic, payloadSerializerFactory); - } + /// + /// Override the with a custom implementation for + /// the specified + /// + /// Name of the topic + /// A factory method that returns a custom implementation + /// of + /// + public void WithPayloadSerializer(string topic, Func payloadSerializerFactory) + { + Builder.WithPayloadSerializer(topic, payloadSerializerFactory); + } - private class DefaultConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) - : ConfigurationSource - { - public override string GetByKey(string key) + private class DefaultConfigurationSource(Microsoft.Extensions.Configuration.IConfiguration configuration) + : ConfigurationSource { - return configuration[key]; + public override string GetByKey(string key) + { + return configuration[key]; + } } } } \ No newline at end of file diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index f72eef98..24207169 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -3,130 +3,131 @@ using Dafda.Producing; using Microsoft.Extensions.DependencyInjection; -namespace Dafda.Configuration; - -/// -/// Extension methods for registering Kafka producers with the Microsoft dependency injection container. -/// -public static class ProducerServiceCollectionExtensions +namespace Dafda.Configuration { /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . Each must be unique — - /// duplicate registrations will throw a . + /// Extension methods for registering Kafka producers with the Microsoft dependency injection container. /// - /// The used in Startup. - /// Use this action to override Dafda and underlying Kafka configuration. - public static void AddProducerFor(this IServiceCollection services, Action options) - where TImplementation : class, TService - where TService : class + public static class ProducerServiceCollectionExtensions { - ThrowIfProducerServiceAlreadyRegisteredFor(services); - - services.AddSingleton(_ => + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . Each must be unique — + /// duplicate registrations will throw a . + /// + /// The used in Startup. + /// Use this action to override Dafda and underlying Kafka configuration. + public static void AddProducerFor(this IServiceCollection services, Action options) + where TImplementation : class, TService + where TService : class { - var producerOptions = new ProducerOptions(); - options?.Invoke(producerOptions); - return new ProducerFactory(producerOptions); - }); - - services.AddTransient(CreateProducerService); - } + ThrowIfProducerServiceAlreadyRegisteredFor(services); - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . Each must be unique — - /// duplicate registrations will throw a . - /// - /// The used in Startup. - /// Use this action to override Dafda and underlying Kafka configuration. - public static void AddProducerFor(this IServiceCollection services, Action options) where TClient : class - { - AddProducerFor(services, options); - } + services.AddSingleton(_ => + { + var producerOptions = new ProducerOptions(); + options?.Invoke(producerOptions); + return new ProducerFactory(producerOptions); + }); + + services.AddTransient(CreateProducerService); + } - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . Each must be unique — - /// duplicate registrations will throw a . - /// - /// Use this overload when configuration depends on other services (for example, IConfiguration). - /// - /// The used in Startup. - /// Factory that creates and configures using the built . - public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) - where TImplementation : class, TService - where TService : class - { - ThrowIfProducerServiceAlreadyRegisteredFor(services); + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . Each must be unique — + /// duplicate registrations will throw a . + /// + /// The used in Startup. + /// Use this action to override Dafda and underlying Kafka configuration. + public static void AddProducerFor(this IServiceCollection services, Action options) where TClient : class + { + AddProducerFor(services, options); + } - services.AddSingleton(provider => + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . Each must be unique — + /// duplicate registrations will throw a . + /// + /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// + /// The used in Startup. + /// Factory that creates and configures using the built . + public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) + where TImplementation : class, TService + where TService : class { - var options = optionsFactory(provider); - return new ProducerFactory(options); - }); + ThrowIfProducerServiceAlreadyRegisteredFor(services); + + services.AddSingleton(provider => + { + var options = optionsFactory(provider); + return new ProducerFactory(options); + }); - services.AddTransient(CreateProducerService); - } + services.AddTransient(CreateProducerService); + } - /// - /// Add a Kafka producer available through the Microsoft dependency injection's - /// as . Each must be unique — - /// duplicate registrations will throw a . - /// - /// Use this overload when configuration depends on other services (for example, IConfiguration). - /// - /// The used in Startup. - /// Factory that creates and configures using the built . - public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) where TClient : class - { - AddProducerFor(services, optionsFactory); - } + /// + /// Add a Kafka producer available through the Microsoft dependency injection's + /// as . Each must be unique — + /// duplicate registrations will throw a . + /// + /// Use this overload when configuration depends on other services (for example, IConfiguration). + /// + /// The used in Startup. + /// Factory that creates and configures using the built . + public static void AddProducerFor(this IServiceCollection services, Func optionsFactory) where TClient : class + { + AddProducerFor(services, optionsFactory); + } - private static void ThrowIfProducerServiceAlreadyRegisteredFor(IServiceCollection services) where TService : class - { - if (services.Any(d => d.ServiceType == typeof(TService))) + private static void ThrowIfProducerServiceAlreadyRegisteredFor(IServiceCollection services) where TService : class { - throw new ProducerFactoryException( - $"A producer has already been registered for service type \"{typeof(TService).FullName}\". Each producer must use a unique service type."); + if (services.Any(d => d.ServiceType == typeof(TService))) + { + throw new ProducerFactoryException( + $"A producer has already been registered for service type \"{typeof(TService).FullName}\". Each producer must use a unique service type."); + } } - } - private static TImplementation CreateProducerService(IServiceProvider provider) - where TImplementation : class, TService - where TService : class - { - var producerFactory = provider.GetRequiredService>(); - var producer = producerFactory.CreateProducerInstance(provider); - return ActivatorUtilities.CreateInstance(provider, producer); + private static TImplementation CreateProducerService(IServiceProvider provider) + where TImplementation : class, TService + where TService : class + { + var producerFactory = provider.GetRequiredService>(); + var producer = producerFactory.CreateProducerInstance(provider); + return ActivatorUtilities.CreateInstance(provider, producer); + } } -} -internal class ProducerFactory(ProducerOptions options) : IDisposable -{ - private readonly ProducerConfiguration _configuration = options.Builder.Build(); - private readonly OutgoingMessageRegistry _messageRegistry = options.OutgoingMessageRegistry; - private KafkaProducer _kafkaProducer; - - - public Producer CreateProducerInstance(IServiceProvider provider) + internal class ProducerFactory(ProducerOptions options) : IDisposable { - _kafkaProducer ??= _configuration.KafkaProducerFactory(provider); + private readonly ProducerConfiguration _configuration = options.Builder.Build(); + private readonly OutgoingMessageRegistry _messageRegistry = options.OutgoingMessageRegistry; + private KafkaProducer _kafkaProducer; + - var producer = new Producer( - kafkaProducer: _kafkaProducer, - outgoingMessageRegistry: _messageRegistry, - messageIdGenerator: _configuration.MessageIdGenerator - ) + public Producer CreateProducerInstance(IServiceProvider provider) { - Name = typeof(TImplementation).FullName - }; + _kafkaProducer ??= _configuration.KafkaProducerFactory(provider); - return producer; - } + var producer = new Producer( + kafkaProducer: _kafkaProducer, + outgoingMessageRegistry: _messageRegistry, + messageIdGenerator: _configuration.MessageIdGenerator + ) + { + Name = typeof(TImplementation).FullName + }; - public void Dispose() - { - _kafkaProducer?.Dispose(); + return producer; + } + + public void Dispose() + { + _kafkaProducer?.Dispose(); + } } } \ No newline at end of file From 6e34e8e2df04c461243ab5c43c101b31941be940 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 17 Mar 2026 08:35:02 +0100 Subject: [PATCH 14/24] revert --- src/Dafda/Producing/ProducerFactoryException.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Dafda/Producing/ProducerFactoryException.cs b/src/Dafda/Producing/ProducerFactoryException.cs index 314cec02..a697faa2 100644 --- a/src/Dafda/Producing/ProducerFactoryException.cs +++ b/src/Dafda/Producing/ProducerFactoryException.cs @@ -1,15 +1,16 @@ using System; using Dafda.Configuration; -namespace Dafda.Producing; - -/// -/// Exception thrown when multiple identical producers are added via -/// . -/// -public sealed class ProducerFactoryException : Exception +namespace Dafda.Producing { - internal ProducerFactoryException(string message) : base(message) + /// + /// Exception thrown when multiple identical producers are added via + /// . + /// + public sealed class ProducerFactoryException : Exception { + internal ProducerFactoryException(string message) : base(message) + { + } } } \ No newline at end of file From 32e716a92e216d4b8c4036c7b3fd8ea48e95a280 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 17 Mar 2026 08:53:33 +0100 Subject: [PATCH 15/24] revert unwanted --- src/Dafda/Producing/ProducerFactoryException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dafda/Producing/ProducerFactoryException.cs b/src/Dafda/Producing/ProducerFactoryException.cs index a697faa2..328ad444 100644 --- a/src/Dafda/Producing/ProducerFactoryException.cs +++ b/src/Dafda/Producing/ProducerFactoryException.cs @@ -5,7 +5,7 @@ namespace Dafda.Producing { /// /// Exception thrown when multiple identical producers are added via - /// . + /// /// public sealed class ProducerFactoryException : Exception { From 5093e177cd10ebd69d181057f40fc205c000574f Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 17 Mar 2026 09:26:21 +0100 Subject: [PATCH 16/24] Revert "revert unwanted" This reverts commit 32e716a92e216d4b8c4036c7b3fd8ea48e95a280. --- src/Dafda/Producing/ProducerFactoryException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dafda/Producing/ProducerFactoryException.cs b/src/Dafda/Producing/ProducerFactoryException.cs index 328ad444..a697faa2 100644 --- a/src/Dafda/Producing/ProducerFactoryException.cs +++ b/src/Dafda/Producing/ProducerFactoryException.cs @@ -5,7 +5,7 @@ namespace Dafda.Producing { /// /// Exception thrown when multiple identical producers are added via - /// + /// . /// public sealed class ProducerFactoryException : Exception { From 7a88fdaa2f809b3e96fad5f431adc3a783148afe Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 17 Mar 2026 09:29:05 +0100 Subject: [PATCH 17/24] fix doc --- src/Dafda/Producing/ProducerFactoryException.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Dafda/Producing/ProducerFactoryException.cs b/src/Dafda/Producing/ProducerFactoryException.cs index a697faa2..8ba231e7 100644 --- a/src/Dafda/Producing/ProducerFactoryException.cs +++ b/src/Dafda/Producing/ProducerFactoryException.cs @@ -4,8 +4,11 @@ namespace Dafda.Producing { /// - /// Exception thrown when multiple identical producers are added via - /// . + /// Exception thrown when multiple identical producers are added via any overload of + /// , + /// , + /// , or + /// . /// public sealed class ProducerFactoryException : Exception { From 8b0eea173e133e78c9578d39d2d64fe0745c85d7 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 24 Mar 2026 13:08:06 +0100 Subject: [PATCH 18/24] fix test --- .../Configuration/TestServiceCollectionExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs index c71c87e7..e5834cc7 100644 --- a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs +++ b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs @@ -112,7 +112,8 @@ public void registers_multiple_typed_producer() Assert.NotNull(messageSenderTwo.ADependency); Assert.Equal("hello two", messageSenderTwo.ADependency.Message); - Assert.Equal(messageSenderTwo.Producer.Name, messageSenderTwo.Producer.Name); + Assert.Equal(typeof(MessageSenderTwo).FullName, messageSenderTwo.Producer.Name); + Assert.NotEqual(messageSenderOne.Producer.Name, messageSenderTwo.Producer.Name); } [Fact] From 582327498b212cd64b97fb63acb635cfb4905e60 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 24 Mar 2026 13:18:32 +0100 Subject: [PATCH 19/24] ProducerFactory --- .../Configuration/TestServiceCollectionExtensions.cs | 4 ++-- .../ProducerServiceCollectionExtensions.cs | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs index e5834cc7..9edd64d9 100644 --- a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs +++ b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs @@ -73,7 +73,7 @@ public void resolves_a_typed_producer_for_an_abstract_service() Assert.NotNull(messageSender); Assert.NotNull(messageSender.Producer); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + Assert.Equal(typeof(IMessageSenderOne).FullName, messageSender.Producer.Name); } [Fact] @@ -201,7 +201,7 @@ public void resolves_a_typed_producer_for_an_abstract_service_with_service_provi Assert.NotNull(messageSender); Assert.NotNull(messageSender.Producer); - Assert.Equal(typeof(MessageSenderOne).FullName, messageSender.Producer.Name); + Assert.Equal(typeof(IMessageSenderOne).FullName, messageSender.Producer.Name); } [Fact] diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index 24207169..d8620c21 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -27,7 +27,7 @@ public static void AddProducerFor(this IServiceCollec { var producerOptions = new ProducerOptions(); options?.Invoke(producerOptions); - return new ProducerFactory(producerOptions); + return new ProducerFactory(producerOptions); }); services.AddTransient(CreateProducerService); @@ -63,7 +63,7 @@ public static void AddProducerFor(this IServiceCollec services.AddSingleton(provider => { var options = optionsFactory(provider); - return new ProducerFactory(options); + return new ProducerFactory(options); }); services.AddTransient(CreateProducerService); @@ -96,19 +96,18 @@ private static TImplementation CreateProducerService( where TImplementation : class, TService where TService : class { - var producerFactory = provider.GetRequiredService>(); + var producerFactory = provider.GetRequiredService>(); var producer = producerFactory.CreateProducerInstance(provider); return ActivatorUtilities.CreateInstance(provider, producer); } } - internal class ProducerFactory(ProducerOptions options) : IDisposable + internal class ProducerFactory(ProducerOptions options) : IDisposable { private readonly ProducerConfiguration _configuration = options.Builder.Build(); private readonly OutgoingMessageRegistry _messageRegistry = options.OutgoingMessageRegistry; private KafkaProducer _kafkaProducer; - public Producer CreateProducerInstance(IServiceProvider provider) { _kafkaProducer ??= _configuration.KafkaProducerFactory(provider); @@ -119,7 +118,7 @@ public Producer CreateProducerInstance(IServiceProvider provider) messageIdGenerator: _configuration.MessageIdGenerator ) { - Name = typeof(TImplementation).FullName + Name = typeof(TService).FullName }; return producer; From c732d273a3728899dcd2a16afd13d4b34b984a2f Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 24 Mar 2026 13:23:44 +0100 Subject: [PATCH 20/24] add test for disposing --- .../TestServiceCollectionExtensions.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs index 9edd64d9..7eb6f4d1 100644 --- a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs +++ b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs @@ -309,6 +309,49 @@ public void producer_factory_is_singleton_reusing_same_kafka_producer_instance_w Assert.Same(senderOne.Producer.KafkaProducer, senderTwo.Producer.KafkaProducer); } + [Fact] + public void disposing_service_provider_disposes_kafka_producer() + { + var spy = new KafkaProducerSpy(); + + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + options.WithKafkaProducerFactory(_ => spy); + }); + + var provider = services.BuildServiceProvider(); + + // Resolve the service so that the KafkaProducer is created inside ProducerFactory + _ = provider.GetRequiredService(); + + Assert.False(spy.WasDisposed); + + provider.Dispose(); + + Assert.True(spy.WasDisposed); + } + + [Fact] + public void disposing_service_provider_does_not_throw_when_kafka_producer_was_never_created() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + + var provider = services.BuildServiceProvider(); + + // Do NOT resolve SimpleSender — KafkaProducer is never instantiated + var exception = Record.Exception(() => provider.Dispose()); + + Assert.Null(exception); + } + [Fact] public void options_factory_receives_service_provider() { From 80e5227a6a1b8c271c0720174c195c287e88e89b Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 24 Mar 2026 13:44:30 +0100 Subject: [PATCH 21/24] refactor Producer to use Lazy for thread-safe initialization --- .../ProducerServiceCollectionExtensions.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index d8620c21..5cccd787 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading; using Dafda.Producing; using Microsoft.Extensions.DependencyInjection; @@ -106,14 +107,18 @@ internal class ProducerFactory(ProducerOptions options) : IDisposable { private readonly ProducerConfiguration _configuration = options.Builder.Build(); private readonly OutgoingMessageRegistry _messageRegistry = options.OutgoingMessageRegistry; - private KafkaProducer _kafkaProducer; + private Lazy _kafkaProducer; public Producer CreateProducerInstance(IServiceProvider provider) { - _kafkaProducer ??= _configuration.KafkaProducerFactory(provider); + // Use LazyInitializer to ensure thread-safe, once-only construction while + // capturing `provider` in the factory delegate. + LazyInitializer.EnsureInitialized( + ref _kafkaProducer, + () => new Lazy(() => _configuration.KafkaProducerFactory(provider), isThreadSafe: true)); var producer = new Producer( - kafkaProducer: _kafkaProducer, + kafkaProducer: _kafkaProducer.Value, outgoingMessageRegistry: _messageRegistry, messageIdGenerator: _configuration.MessageIdGenerator ) @@ -126,7 +131,10 @@ public Producer CreateProducerInstance(IServiceProvider provider) public void Dispose() { - _kafkaProducer?.Dispose(); + if (_kafkaProducer is { IsValueCreated: true }) + { + _kafkaProducer.Value.Dispose(); + } } } } \ No newline at end of file From 9697f764f1d249fd3c9997bd5abd08b88fcd78c7 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 24 Mar 2026 14:33:16 +0100 Subject: [PATCH 22/24] copilot --- .../TestServiceCollectionExtensions.cs | 82 +++++++++++++++++++ .../ProducerServiceCollectionExtensions.cs | 14 ++-- .../Producing/ProducerFactoryException.cs | 7 +- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs index 7eb6f4d1..93a399a8 100644 --- a/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs +++ b/src/Dafda.Tests/Configuration/TestServiceCollectionExtensions.cs @@ -352,6 +352,88 @@ public void disposing_service_provider_does_not_throw_when_kafka_producer_was_ne Assert.Null(exception); } + [Fact] + public void throws_when_same_service_type_is_registered_twice_with_action_options() + { + var services = new ServiceCollection(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + + var exception = Assert.Throws(() => + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + })); + + Assert.Contains(typeof(SimpleSender).FullName, exception.Message); + } + + [Fact] + public void throws_when_same_service_type_is_registered_twice_with_factory_options() + { + var services = new ServiceCollection(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + + var exception = Assert.Throws(() => + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + })); + + Assert.Contains(typeof(SimpleSender).FullName, exception.Message); + } + + [Fact] + public void throws_when_same_abstract_service_type_is_registered_twice_with_action_options() + { + var services = new ServiceCollection(); + services.AddTransient(); + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + }); + + var exception = Assert.Throws(() => + services.AddProducerFor(options => + { + options.WithBootstrapServers("dummy"); + })); + + Assert.Contains(typeof(IMessageSenderOne).FullName, exception.Message); + } + + [Fact] + public void throws_when_same_abstract_service_type_is_registered_twice_with_factory_options() + { + var services = new ServiceCollection(); + services.AddTransient(); + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + }); + + var exception = Assert.Throws(() => + services.AddProducerFor(_ => + { + var options = new ProducerOptions(); + options.WithBootstrapServers("dummy"); + return options; + })); + + Assert.Contains(typeof(IMessageSenderOne).FullName, exception.Message); + } + [Fact] public void options_factory_receives_service_provider() { diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index 5cccd787..b2fb05ea 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -24,11 +24,11 @@ public static void AddProducerFor(this IServiceCollec { ThrowIfProducerServiceAlreadyRegisteredFor(services); - services.AddSingleton(_ => + services.AddSingleton(provider => { var producerOptions = new ProducerOptions(); options?.Invoke(producerOptions); - return new ProducerFactory(producerOptions); + return new ProducerFactory(provider, producerOptions); }); services.AddTransient(CreateProducerService); @@ -64,7 +64,7 @@ public static void AddProducerFor(this IServiceCollec services.AddSingleton(provider => { var options = optionsFactory(provider); - return new ProducerFactory(options); + return new ProducerFactory(provider, options); }); services.AddTransient(CreateProducerService); @@ -89,7 +89,7 @@ private static void ThrowIfProducerServiceAlreadyRegisteredFor(IServic if (services.Any(d => d.ServiceType == typeof(TService))) { throw new ProducerFactoryException( - $"A producer has already been registered for service type \"{typeof(TService).FullName}\". Each producer must use a unique service type."); + $"Service type \"{typeof(TService).FullName}\" is already registered in the dependency injection container. Each Dafda producer must use a unique service type."); } } @@ -98,18 +98,18 @@ private static TImplementation CreateProducerService( where TService : class { var producerFactory = provider.GetRequiredService>(); - var producer = producerFactory.CreateProducerInstance(provider); + var producer = producerFactory.CreateProducerInstance(); return ActivatorUtilities.CreateInstance(provider, producer); } } - internal class ProducerFactory(ProducerOptions options) : IDisposable + internal class ProducerFactory(IServiceProvider provider, ProducerOptions options) : IDisposable { private readonly ProducerConfiguration _configuration = options.Builder.Build(); private readonly OutgoingMessageRegistry _messageRegistry = options.OutgoingMessageRegistry; private Lazy _kafkaProducer; - public Producer CreateProducerInstance(IServiceProvider provider) + public Producer CreateProducerInstance() { // Use LazyInitializer to ensure thread-safe, once-only construction while // capturing `provider` in the factory delegate. diff --git a/src/Dafda/Producing/ProducerFactoryException.cs b/src/Dafda/Producing/ProducerFactoryException.cs index 8ba231e7..0f353c52 100644 --- a/src/Dafda/Producing/ProducerFactoryException.cs +++ b/src/Dafda/Producing/ProducerFactoryException.cs @@ -4,11 +4,14 @@ namespace Dafda.Producing { /// - /// Exception thrown when multiple identical producers are added via any overload of + /// Exception thrown when the service type used to identify a Dafda producer is already present in the + /// at the time /// , /// , /// , or - /// . + /// + /// is called. Each Dafda producer requires a service type that is not yet registered — the conflicting + /// registration may come from another AddProducerFor call or from any other source. /// public sealed class ProducerFactoryException : Exception { From f41389eeb1966e08ba313c91ea54af3c166525ae Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 24 Mar 2026 14:43:58 +0100 Subject: [PATCH 23/24] copilot --- src/Dafda/Configuration/OutboxProducerOptions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Dafda/Configuration/OutboxProducerOptions.cs b/src/Dafda/Configuration/OutboxProducerOptions.cs index b162be5f..e2930c6f 100644 --- a/src/Dafda/Configuration/OutboxProducerOptions.cs +++ b/src/Dafda/Configuration/OutboxProducerOptions.cs @@ -2,7 +2,6 @@ using Dafda.Outbox; using Dafda.Producing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace Dafda.Configuration { From c1c0992d02477b265aeb2dfd18597d06e25344e8 Mon Sep 17 00:00:00 2001 From: Nabi Sobhi Date: Tue, 24 Mar 2026 14:48:39 +0100 Subject: [PATCH 24/24] build again --- src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs index b2fb05ea..bb7e2016 100644 --- a/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs +++ b/src/Dafda/Configuration/ProducerServiceCollectionExtensions.cs @@ -137,4 +137,4 @@ public void Dispose() } } } -} \ No newline at end of file +}