Example #1
0
        private async Task OnMessageReceivedAsync <TEvent, TConsumer>(EventRegistration ereg,
                                                                      EventConsumerRegistration creg,
                                                                      IModel channel,
                                                                      BasicDeliverEventArgs args,
                                                                      CancellationToken cancellationToken)
            where TEvent : class
            where TConsumer : IEventConsumer <TEvent>
        {
            var messageId = args.BasicProperties?.MessageId;

            using var log_scope = Logger.BeginScopeForConsume(id: messageId,
                                                              correlationId: args.BasicProperties?.CorrelationId,
                                                              extras: new Dictionary <string, string>
            {
                ["RoutingKey"]  = args.RoutingKey,
                ["DeliveryTag"] = args.DeliveryTag.ToString(),
            });

            args.BasicProperties.Headers.TryGetValue(AttributeNames.ActivityId, out var parentActivityId);

            // Instrumentation
            using var activity = EventBusActivitySource.StartActivity(ActivityNames.Consume, ActivityKind.Consumer, parentActivityId?.ToString());
            activity?.AddTag(ActivityTagNames.EventBusEventType, typeof(TEvent).FullName);
            activity?.AddTag(ActivityTagNames.EventBusConsumerType, typeof(TConsumer).FullName);
            activity?.AddTag(ActivityTagNames.MessagingSystem, Name);
            activity?.AddTag(ActivityTagNames.MessagingDestination, creg.ConsumerName);
            activity?.AddTag(ActivityTagNames.MessagingDestinationKind, "queue"); // only queues are possible

            try
            {
                Logger.LogDebug("Processing '{MessageId}'", messageId);
                using var scope = CreateScope();
                using var ms    = new MemoryStream(args.Body.ToArray());
                var contentType = GetContentType(args.BasicProperties);
                var context     = await DeserializeAsync <TEvent>(body : ms,
                                                                  contentType : contentType,
                                                                  registration : ereg,
                                                                  scope : scope,
                                                                  cancellationToken : cancellationToken);

                Logger.LogInformation("Received message: '{MessageId}' containing Event '{Id}'",
                                      messageId,
                                      context.Id);
                await ConsumeAsync <TEvent, TConsumer>(@event : context,
                                                       scope : scope,
                                                       cancellationToken : cancellationToken);

                // Acknowlege the message
                Logger.LogDebug("Completing message: {MessageId}, {DeliveryTag}.", messageId, args.DeliveryTag);
                channel.BasicAck(deliveryTag: args.DeliveryTag, multiple: false);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Event processing failed. Moving to deadletter.");
                channel.BasicNack(deliveryTag: args.DeliveryTag, multiple: false, requeue: false);
            }
        }
Example #2
0
 /// <summary>
 /// Get the consumer registration in a given event type.
 /// </summary>
 /// <typeparam name="TEvent">The event type from wich to retrieve a <see cref="EventConsumerRegistration"/> for.</typeparam>
 /// <typeparam name="TConsumer">The consumer to configure.</typeparam>
 /// <param name="registration">
 /// When this method returns, contains the consumer registration associated with the specified event type,
 /// if the event type is found; otherwise, <see langword="null"/> is returned.
 /// This parameter is passed uninitialized.
 /// </param>
 /// <returns><see langword="true" /> if there's a consumer registered for the given event type; otherwise, false.</returns>
 public bool TryGetConsumerRegistration <TEvent, TConsumer>(out EventConsumerRegistration registration)
 {
     registration = default;
     if (Registrations.TryGetValue(typeof(TEvent), out var ereg))
     {
         registration = ereg.Consumers.SingleOrDefault(cr => cr.ConsumerType == typeof(TConsumer));
         if (registration != null)
         {
             return(true);
         }
     }
     return(false);
 }
        private async Task OnMessageReceivedAsync <TEvent, TConsumer>(EventRegistration ereg, EventConsumerRegistration creg, ProcessMessageEventArgs args)
            where TEvent : class
            where TConsumer : IEventConsumer <TEvent>
        {
            var message           = args.Message;
            var messageId         = message.MessageId;
            var cancellationToken = args.CancellationToken;

            message.ApplicationProperties.TryGetValue(AttributeNames.ActivityId, out var parentActivityId);

            using var log_scope = Logger.BeginScopeForConsume(id: messageId,
                                                              correlationId: message.CorrelationId,
                                                              sequenceNumber: message.SequenceNumber,
                                                              extras: new Dictionary <string, string>
            {
                ["EnqueuedSequenceNumber"] = message.EnqueuedSequenceNumber.ToString(),
            });

            // Instrumentation
            using var activity = EventBusActivitySource.StartActivity(ActivityNames.Consume, ActivityKind.Consumer, parentActivityId?.ToString());
            activity?.AddTag(ActivityTagNames.EventBusEventType, typeof(TEvent).FullName);
            activity?.AddTag(ActivityTagNames.EventBusConsumerType, typeof(TConsumer).FullName);
            activity?.AddTag(ActivityTagNames.MessagingSystem, Name);
            var destination = TransportOptions.UseBasicTier || ereg.UseQueueInsteadOfTopic() ? ereg.EventName : creg.ConsumerName;

            activity?.AddTag(ActivityTagNames.MessagingDestination, destination); // name of the queue/subscription
            activity?.AddTag(ActivityTagNames.MessagingDestinationKind, "queue"); // the spec does not know subscription so we can only use queue for both

            try
            {
                Logger.LogDebug("Processing '{MessageId}'", messageId);
                using var scope = CreateScope();
                using var ms    = message.Body.ToStream();
                var contentType = new ContentType(message.ContentType);
                var context     = await DeserializeAsync <TEvent>(body : ms,
                                                                  contentType : contentType,
                                                                  registration : ereg,
                                                                  scope : scope,
                                                                  cancellationToken : cancellationToken);

                Logger.LogInformation("Received message: '{MessageId}' containing Event '{Id}'",
                                      messageId,
                                      context.Id);

                await ConsumeAsync <TEvent, TConsumer>(@event : context,
                                                       scope : scope,
                                                       cancellationToken : cancellationToken);

                // Complete the message
                Logger.LogDebug("Completing message: {MessageId}.", messageId);
                await args.CompleteMessageAsync(message : message, cancellationToken : cancellationToken);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Event processing failed. Moving to deadletter.");
                await args.DeadLetterMessageAsync(message : message, cancellationToken : cancellationToken);
            }
        }
        private async Task <ServiceBusProcessor> GetProcessorAsync(EventRegistration ereg, EventConsumerRegistration creg, CancellationToken cancellationToken)
        {
            await processorsCacheLock.WaitAsync(cancellationToken);

            try
            {
                var topicName        = ereg.EventName;
                var subscriptionName = creg.ConsumerName;

                var key = $"{topicName}/{subscriptionName}";
                if (!processorsCache.TryGetValue(key, out var processor))
                {
                    // Create the processor options
                    var sbpo = TransportOptions.CreateProcessorOptions?.Invoke() ?? new ServiceBusProcessorOptions();

                    // Maximum number of concurrent calls to the callback ProcessMessagesAsync(), set to 1 for simplicity.
                    // Set it according to how many messages the application wants to process in parallel.
                    sbpo.MaxConcurrentCalls = 1;

                    // Indicates whether MessagePump should automatically complete the messages after returning from User Callback.
                    // False below indicates the Complete will be handled by the User Callback as in `ProcessMessagesAsync` below.
                    sbpo.AutoCompleteMessages = false;

                    // Create the processor. Queues are used in the basic tier or when explicitly mapped to Queue.
                    // Otherwise, Topics and Subscriptions are used.
                    if (TransportOptions.UseBasicTier || ereg.UseQueueInsteadOfTopic())
                    {
                        // Ensure Queue is created
                        await CreateQueueIfNotExistsAsync(name : topicName, cancellationToken : cancellationToken);

                        // Create the processor for the Queue
                        Logger.LogDebug("Creating processor for queue '{QueueName}'", topicName);
                        processor = serviceBusClient.CreateProcessor(queueName: topicName, options: sbpo);
                    }
                    else
                    {
                        // Ensure Topic is created before creating the Subscription
                        await CreateTopicIfNotExistsAsync(name : topicName, cancellationToken : cancellationToken);

                        // Ensure Subscription is created
                        await CreateSubscriptionIfNotExistsAsync(topicName : topicName,
                                                                 subscriptionName : subscriptionName,
                                                                 cancellationToken : cancellationToken);

                        // Create the processor for the Subscription
                        Logger.LogDebug("Creating processor for topic '{TopicName}' and subscription '{Subscription}'",
                                        topicName,
                                        subscriptionName);
                        processor = serviceBusClient.CreateProcessor(topicName: topicName,
                                                                     subscriptionName: subscriptionName,
                                                                     options: sbpo);
                    }

                    processorsCache[key] = processor;
                }

                return(processor);
            }
            finally
            {
                processorsCacheLock.Release();
            }
        }