44using OpenDDD . Infrastructure . Events . Base ;
55using OpenDDD . Infrastructure . Events . RabbitMq . Factories ;
66using RabbitMQ . Client ;
7+ using RabbitMQ . Client . Exceptions ;
78
89namespace OpenDDD . Infrastructure . Events . RabbitMq
910{
@@ -12,17 +13,20 @@ public class RabbitMqMessagingProvider : IMessagingProvider, IAsyncDisposable
1213 private readonly IConnectionFactory _connectionFactory ;
1314 private readonly IRabbitMqConsumerFactory _consumerFactory ;
1415 private readonly ILogger < RabbitMqMessagingProvider > _logger ;
16+ private readonly bool _autoCreateTopics ;
1517 private IConnection ? _connection ;
1618 private IChannel ? _channel ;
1719 private readonly ConcurrentDictionary < string , RabbitMqSubscription > _subscriptions = new ( ) ;
1820
1921 public RabbitMqMessagingProvider (
2022 IConnectionFactory factory ,
2123 IRabbitMqConsumerFactory consumerFactory ,
24+ bool autoCreateTopics ,
2225 ILogger < RabbitMqMessagingProvider > logger )
2326 {
2427 _connectionFactory = factory ?? throw new ArgumentNullException ( nameof ( factory ) ) ;
2528 _consumerFactory = consumerFactory ?? throw new ArgumentNullException ( nameof ( consumerFactory ) ) ;
29+ _autoCreateTopics = autoCreateTopics ;
2630 _logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
2731 }
2832
@@ -41,6 +45,22 @@ public async Task<ISubscription> SubscribeAsync(string topic, string consumerGro
4145
4246 if ( _channel is null ) throw new InvalidOperationException ( "RabbitMQ channel is not available." ) ;
4347
48+ bool exchangeExists = await ExchangeExistsAsync ( topic , cancellationToken ) ;
49+
50+ if ( ! exchangeExists )
51+ {
52+ if ( _autoCreateTopics )
53+ {
54+ await _channel . ExchangeDeclareAsync ( topic , ExchangeType . Fanout , durable : true , autoDelete : false , cancellationToken : cancellationToken ) ;
55+ _logger . LogInformation ( "Auto-created exchange (topic): {Topic}" , topic ) ;
56+ }
57+ else
58+ {
59+ _logger . LogError ( "Cannot subscribe to non-existent topic: {Topic}" , topic ) ;
60+ throw new InvalidOperationException ( $ "Topic '{ topic } ' does not exist.") ;
61+ }
62+ }
63+
4464 await _channel . ExchangeDeclareAsync ( topic , ExchangeType . Fanout , durable : true , autoDelete : false , cancellationToken : cancellationToken ) ;
4565 var queueName = $ "{ consumerGroup } .{ topic } ";
4666 await _channel . QueueDeclareAsync ( queueName , durable : true , exclusive : false , autoDelete : false , cancellationToken : cancellationToken ) ;
@@ -87,7 +107,21 @@ public async Task PublishAsync(string topic, string message, CancellationToken c
87107
88108 if ( _channel is null ) throw new InvalidOperationException ( "RabbitMQ channel is not available." ) ;
89109
90- await _channel . ExchangeDeclareAsync ( topic , ExchangeType . Fanout , durable : true , autoDelete : false , cancellationToken : cancellationToken ) ;
110+ bool exchangeExists = await ExchangeExistsAsync ( topic , cancellationToken ) ;
111+
112+ if ( ! exchangeExists )
113+ {
114+ if ( _autoCreateTopics )
115+ {
116+ await _channel . ExchangeDeclareAsync ( topic , ExchangeType . Fanout , durable : true , autoDelete : false , cancellationToken : cancellationToken ) ;
117+ _logger . LogInformation ( "Auto-created exchange (topic): {Topic}" , topic ) ;
118+ }
119+ else
120+ {
121+ _logger . LogError ( "Cannot publish to non-existent topic: {Topic}" , topic ) ;
122+ throw new InvalidOperationException ( $ "Topic '{ topic } ' does not exist.") ;
123+ }
124+ }
91125
92126 var body = Encoding . UTF8 . GetBytes ( message ) ;
93127 await _channel . BasicPublishAsync ( topic , "" , body , cancellationToken : cancellationToken ) ;
@@ -102,6 +136,29 @@ private async Task EnsureConnectedAsync(CancellationToken cancellationToken)
102136 _connection = await _connectionFactory . CreateConnectionAsync ( cancellationToken ) ;
103137 _channel = await _connection . CreateChannelAsync ( null , cancellationToken ) ;
104138 }
139+
140+ private async Task < bool > ExchangeExistsAsync ( string exchange , CancellationToken cancellationToken )
141+ {
142+ try
143+ {
144+ await _channel ! . ExchangeDeclarePassiveAsync ( exchange , cancellationToken ) ;
145+ return true ;
146+ }
147+ catch ( OperationInterruptedException ex ) when ( ex . ShutdownReason ? . ReplyCode == 404 )
148+ {
149+ _logger . LogDebug ( "Exchange '{Exchange}' does not exist." , exchange ) ;
150+
151+ // Since the channel was closed, reopen it before returning
152+ await EnsureConnectedAsync ( cancellationToken ) ;
153+
154+ return false ;
155+ }
156+ catch ( Exception ex )
157+ {
158+ _logger . LogError ( ex , "Unexpected error while checking if exchange '{Exchange}' exists." , exchange ) ;
159+ throw ;
160+ }
161+ }
105162
106163 public async ValueTask DisposeAsync ( )
107164 {
0 commit comments