/// <summary> /// Consumer constructor /// </summary> /// <param name="configuration">constructor configuration</param> /// <param name="logger">Logger</param> internal RabbitBase(RabbitConsumerConfiguration configuration, ILogger logger = null) : this((RabbitConfigurationBase)configuration, logger) { // Fixings configuration.Workers = configuration.Workers < 1 ? (ushort)1 : configuration.Workers; configuration.BatchSize = configuration.BatchSize < 1 ? (ushort)1 : configuration.BatchSize; configuration.RetryIntervals ??= new List <ulong> { }; configuration.RetryIntervals = configuration.RetryIntervals .Where(interval => interval >= 5000) .OrderBy(interval => interval) .ToList(); // Validations var invalid = configuration.Dependencies.Count == 0 ? string.Empty : configuration.Dependencies.Any(dependency => string.IsNullOrEmpty(dependency.Name)) ? "Name" : configuration.Dependencies.Any(dependency => string.IsNullOrEmpty(dependency.Type)) ? "Type" : string.Empty; if (!string.IsNullOrWhiteSpace(invalid)) { throw new ArgumentException(RabbitAnnotations.Exception.FactoryArgumentExceptionConsumer, "Dependecies." + invalid); } if (configuration.Dependencies.Any(dependency => !AllowedExchangeTypes.Contains(dependency.Type))) { throw new NotImplementedException(RabbitAnnotations.Exception.FactoryNotImplementedExceptionConsumer); } // Preparations var retry = configuration.RetryExchangeConfiguration; var exchanges = new List <RabbitPublisherConfiguration> { retry }; exchanges.AddRange(configuration.Dependencies.Select(dependency => new RabbitPublisherConfiguration { Name = dependency.Name, Type = dependency.Type, Arguments = dependency.Arguments })); var bindings = configuration.Dependencies .Select(dependency => new RabbitConfigurationDependency { Name = dependency.Name, Route = string.IsNullOrEmpty(dependency.Route) ? configuration.Name : dependency.Route }) .ToDictionary(dependency => dependency.Name, dependency => dependency.Route); bindings.Add(retry.Name, configuration.Name); // Declarations for (int i = 1; i <= configuration.Workers; i++) { // Declaring channel var channel = DeclareChannel(configuration); // Declaring paired exchanges foreach (var exchange in exchanges) { DeclareExchange(channel, exchange.Name, exchange.Type, exchange.Arguments); } // Declaring queue and bindings DeclareQueue(channel, configuration.Name, bindings); // Registering channel Channels.Add(channel); } }
/// <summary> /// Constructor /// </summary> /// <param name="configuration">Configuration</param> /// <param name="logger">Logger</param> public RabbitConsumer(RabbitConsumerConfiguration configuration, ILogger logger = null) : base(configuration, logger) { // ... Configuration = configuration; Retry = new RetryPublisher(Configuration.RetryExchangeConfiguration); // Creating consumers (a.k.a workers) for (short i = 0; i < Channels.Count; i++) { // Current channel var channel = Channels[i]; // Declaring consumer var consumer = new RabbitConsumerBase <T>(channel, Configuration.BatchSize); OnStart(i); // The consumer will not recive messages, until current is not handled channel.BasicQos(0, Configuration.BatchSize, false); // Binding queue to the consumer // Message(s) will not be deleted automatically // The consumer will wait acknowledgement to do so channel.BasicConsume(Configuration.Name, false, consumer); // Action for recieve message(s) consumer.Received += (sender, package) => { // Defining the package body var body = Encoding.UTF8.GetString(package.Body.ToArray()); // Trying to preserve the body as actual message try { // Convert to actual typed message var message = JsonSerializer.Deserialize <T>(body, JsonOptions); // Defining the next delay var previous = (object)0; if (package.BasicProperties.Headers != null) { package.BasicProperties.Headers.TryGetValue(RabbitAnnotations.Retry.HeaderKey, out previous); } var delay = Configuration.RetryIntervals.FirstOrDefault(interval => interval > (ulong)Math.Abs(Convert.ToInt64(previous)) ); // Preserve the message, make it ready for handling consumer.Preserve(message, package.DeliveryTag, delay); } // If something went wrong on this stage ... catch (Exception exception) { // Retrying it does not make sence Task.Run(async() => { // We do kill the message await Handle(exception, new List <string> { body }); channel.BasicAck(package.DeliveryTag, false); }); } }; // Action for handle message(s) consumer.Handle += (packages, tag) => { // Run hendling Task.Run(async() => { // Trying to handle message(s) try { // Handle beggin time var stamp = DateTime.Now; // Messages var messages = packages.Select(m => m.Message); // Handling single message if (Configuration.BatchSize == 1) { await Handle(packages[0].Message); } // Handling grouped messages else { await Handle(packages.Select(m => m.Message)); } OnHandled((DateTime.Now - stamp).TotalMilliseconds, messages.Count()); } // In case if something goes wrong // We either retry the message(s) or kill them catch (Exception exception) { // Handle retry await Handle( exception, packages.Where(package => package.Delay > 0) ); // Handle dead await Handle( exception, packages.Where(package => package.Delay == 0) .Select(package => JsonSerializer.Serialize(package.Message, JsonOptions)) ); } // Message(s) will be deleted from the queue finally { // Suggestion, that everything is ok and we can delete the message(s) from the queue channel.BasicAck(tag, true); } }); }; } }