11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using Aspire . Kafka ;
5+ using System . Text . Json ;
46using Aspire . Kafka . Consumer ;
57using Confluent . Kafka ;
68using Microsoft . Extensions . Configuration ;
79using Microsoft . Extensions . DependencyInjection ;
10+ using Microsoft . Extensions . DependencyInjection . Extensions ;
11+ using Microsoft . Extensions . Logging ;
812
913namespace Microsoft . Extensions . Hosting ;
1014
@@ -19,68 +23,109 @@ public static class AspireKafkaConsumerExtensions
1923 /// Registers <see cref="IConsumer{TKey,TValue}"/> as a singleton in the services provided by the <paramref name="builder"/>.
2024 /// </summary>
2125 /// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
22- /// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param> ///
26+ /// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
27+ /// <param name="configureKafkaConsumerSettings"></param>
2328 /// <param name="configureConsumerBuilder">A method used for customizing the <see cref="ConsumerBuilder{TKey,TValue}"/>.</param>
2429 /// <param name="configureConsumerConfig">An optional method that can be used for customizing the <see cref="ConsumerConfig"/>. It's invoked after the settings are read from the configuration.</param>
2530 /// <remarks>Reads the configuration from "Aspire:Kafka:Consumer" section.</remarks>
26- public static void AddKafkaConsumer < TKey , TValue > ( this IHostApplicationBuilder builder , string connectionName , Action < ConsumerBuilder < TKey , TValue > > ? configureConsumerBuilder = null , Action < ConsumerConfig > ? configureConsumerConfig = null )
27- => AddKafkaConsumer ( builder , DefaultConfigSectionName , configureConsumerBuilder , configureConsumerConfig , connectionName , serviceKey : null ) ;
31+ public static void AddKafkaConsumer < TKey , TValue > ( this IHostApplicationBuilder builder , string connectionName , Action < KafkaConsumerSettings > ? configureKafkaConsumerSettings = null , Action < ConsumerBuilder < TKey , TValue > > ? configureConsumerBuilder = null , Action < ConsumerConfig > ? configureConsumerConfig = null )
32+ => AddKafkaConsumer ( builder , DefaultConfigSectionName , configureKafkaConsumerSettings , configureConsumerBuilder , configureConsumerConfig , connectionName , serviceKey : null ) ;
2833
2934 /// <summary>
3035 /// Registers <see cref="IConsumer{TKey,TValue}"/> as a keyed singleton for the given <paramref name="name"/> in the services provided by the <paramref name="builder"/>.
3136 /// </summary>
3237 /// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
3338 /// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection string from the ConnectionStrings configuration section.</param>
39+ /// <param name="configureKafkaConsumerSettings"></param>
3440 /// <param name="configureConsumerBuilder">A method used for customizing the <see cref="ConsumerBuilder{TKey,TValue}"/>.</param>
3541 /// <param name="configureConsumerConfig">An optional method that can be used for customizing the <see cref="ConsumerConfig"/>. It's invoked after the settings are read from the configuration.</param>
3642 /// <remarks>Reads the configuration from "Aspire:Kafka:Consumer:{name}" section.</remarks>
37- public static void AddKeyedKafkaConsumer < TKey , TValue > ( this IHostApplicationBuilder builder , string name , Action < ConsumerBuilder < TKey , TValue > > ? configureConsumerBuilder = null , Action < ConsumerConfig > ? configureConsumerConfig = null )
43+ public static void AddKeyedKafkaConsumer < TKey , TValue > ( this IHostApplicationBuilder builder , string name , Action < KafkaConsumerSettings > ? configureKafkaConsumerSettings = null , Action < ConsumerBuilder < TKey , TValue > > ? configureConsumerBuilder = null , Action < ConsumerConfig > ? configureConsumerConfig = null )
3844 {
3945 ArgumentException . ThrowIfNullOrEmpty ( name ) ;
4046
41- AddKafkaConsumer ( builder , $ "{ DefaultConfigSectionName } :{ name } ", configureConsumerBuilder , configureConsumerConfig , connectionName : name , serviceKey : name ) ;
47+ AddKafkaConsumer ( builder , $ "{ DefaultConfigSectionName } :{ name } ", configureKafkaConsumerSettings , configureConsumerBuilder , configureConsumerConfig , connectionName : name , serviceKey : name ) ;
4248 }
4349
4450 private static void AddKafkaConsumer < TKey , TValue > (
4551 IHostApplicationBuilder builder ,
4652 string configurationSectionName ,
53+ Action < KafkaConsumerSettings > ? configureKafkaConsumerSettings ,
4754 Action < ConsumerBuilder < TKey , TValue > > ? configureConsumerBuilder ,
4855 Action < ConsumerConfig > ? configureConsumerConfig ,
4956 string connectionName ,
5057 string ? serviceKey )
5158 {
5259 ArgumentNullException . ThrowIfNull ( builder ) ;
5360
54- var configSection = builder . Configuration . GetSection ( configurationSectionName ) ;
55-
5661 ConsumerConfig config = new ( ) ;
62+ // First get the config from the config section
63+ var configSection = builder . Configuration . GetSection ( $ "{ configurationSectionName } :Config") ;
5764 configSection . Bind ( config ) ;
65+ // Then override with the optional configureProducerConfig
66+ configureConsumerConfig ? . Invoke ( config ) ;
5867
68+ KafkaConsumerSettings settings = new ( ) ;
69+ // First get the settings from the config section
70+ var kafkaProducerSettingsSection = builder . Configuration . GetSection ( $ "{ configurationSectionName } ") ;
71+ kafkaProducerSettingsSection . Bind ( settings ) ;
5972 if ( builder . Configuration . GetConnectionString ( connectionName ) is string connectionString )
6073 {
61- config . BootstrapServers = connectionString ;
74+ settings . ConnectionString = connectionString ;
6275 }
76+ // Then override with the optional configureProducerSettings
77+ configureKafkaConsumerSettings ? . Invoke ( settings ) ;
6378
64- configureConsumerConfig ? . Invoke ( config ) ;
79+ // Apply the settings to the config
80+ settings . Apply ( config ) ;
6581
66- ConsumerBuilder < TKey , TValue > CreateConsumerBuilder ( )
82+ ConsumerBuilder < TKey , TValue > CreateConsumerBuilder ( IServiceProvider serviceProvider )
6783 {
6884 ConsumerBuilder < TKey , TValue > consumerBuilder = new ( config ) ;
6985 configureConsumerBuilder ? . Invoke ( consumerBuilder ) ;
86+ if ( settings . Metrics )
87+ {
88+ consumerBuilder . SetStatisticsHandler ( BuildStatisticsHandler < TKey , TValue > ( serviceProvider ) ) ;
89+ }
7090 return consumerBuilder ;
7191 }
7292
73- ConsumerConnectionFactory < TKey , TValue > consumerConnectionFactory = new ( CreateConsumerBuilder ( ) , config ) ;
74-
7593 if ( serviceKey is null )
7694 {
77- builder . Services . AddSingleton < ConsumerConnectionFactory < TKey , TValue > > ( _ => consumerConnectionFactory ) ;
95+ builder . Services . AddSingleton < ConsumerConnectionFactory < TKey , TValue > > ( sp => new ( CreateConsumerBuilder ( sp ) , config ) ) ;
7896 builder . Services . AddSingleton < IConsumer < TKey , TValue > > ( sp => sp . GetRequiredService < ConsumerConnectionFactory < TKey , TValue > > ( ) . Create ( ) ) ;
7997 }
8098 else
8199 {
82- builder . Services . AddKeyedSingleton < ConsumerConnectionFactory < TKey , TValue > > ( serviceKey , ( sp , key ) => consumerConnectionFactory ) ;
100+ builder . Services . AddKeyedSingleton < ConsumerConnectionFactory < TKey , TValue > > ( serviceKey , ( sp , key ) => new ( CreateConsumerBuilder ( sp ) , config ) ) ;
83101 builder . Services . AddKeyedSingleton < IConsumer < TKey , TValue > > ( serviceKey , ( sp , key ) => sp . GetRequiredKeyedService < ConsumerConnectionFactory < TKey , TValue > > ( key ) . Create ( ) ) ;
84102 }
103+
104+ if ( settings . Metrics )
105+ {
106+ builder . Services . TryAddSingleton < ConsumerMetrics > ( ) ;
107+ builder . Services . AddOpenTelemetry ( )
108+ . WithMetrics ( metricBuilderProvider => metricBuilderProvider . AddMeter ( ConsumerMetrics . MeterName ) ) ;
109+ }
110+ }
111+
112+ private static Action < IConsumer < TKey , TValue > , string > BuildStatisticsHandler < TKey , TValue > ( IServiceProvider sp )
113+ {
114+ ConsumerMetrics metrics = sp . GetRequiredService < ConsumerMetrics > ( ) ;
115+ return ( _ , json ) =>
116+ {
117+ if ( string . IsNullOrEmpty ( json ) )
118+ {
119+ return ;
120+ }
121+
122+ Statistics ? statistics = JsonSerializer . Deserialize ( json , typeof ( Statistics ) , StatisticsJsonSerializerContext . Default ) as Statistics ;
123+ if ( statistics == null )
124+ {
125+ return ;
126+ }
127+
128+ metrics . Update ( statistics ) ;
129+ } ;
85130 }
86131}
0 commit comments