using System; using System.Threading; using System.Threading.Tasks; using Confluent.Kafka; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using xCommons.Extensions; using xEventService.Configuration; using xEventService.Constants; using xEventService.Extensions; using xExceptions.Constants; namespace xEventService.Providers { /// /// an Abstract Kafka Consumer Service ... /// public abstract class XKafkaConsumerServiceBase : BackgroundService { /// /// Consumed Topic Messages ... /// protected readonly string topic; /// /// Check is Paused ... /// private bool paused = false; /// /// Allowed Senders ... /// public readonly string[] allowedSenders; /// /// Allowed Actions ... /// public readonly string[] allowedActions; /// /// Kafka Consumer Class ... /// protected readonly IConsumer consumer; protected readonly XEventServiceConfiguration configuration; protected readonly ILogger logger; protected XKafkaConsumerServiceBase( string[] allowedActions, string[] allowedSenders, XEventServiceConfiguration configuration, ILogger logger, string topic = XEventServiceConstants.XDefaultTopic ) { // this.topic = topic; this.logger = logger; this.configuration = configuration; // // Validate Actions ... if (!allowedActions .IsAllowedCollection()) { XException.InvalidArgs.Throw(); } this.allowedActions = [.. allowedActions]; // // Validate Senders ... if (!allowedSenders .IsAllowedCollection()) { XException.InvalidArgs.Throw(); } this.allowedSenders = [.. allowedSenders]; // // Prepare Kafka Consumer Configuration ... var config = new ConsumerConfig { EnableAutoCommit = false, BootstrapServers = configuration.Url, AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = XEventServiceConstants.XDefaultConsumerGroup, }; consumer = new ConsumerBuilder(config).Build(); } /// /// Pause Consuming ... /// /// public bool Pause() { // var result = !paused; if (!result) { return result; } // paused = true; result = paused; return result; } /// /// Resume Consuming ... /// /// public bool Resume() { // var result = paused; if (!result) { return result; } // paused = false; result = !paused; return result; } /// /// Default Action Execution for Background Services ... /// /// /// protected override async Task ExecuteAsync( CancellationToken cancellationToken = default ) { // // For Fix Blocking Synchronous ... await Task.Yield(); using var cts = new CancellationTokenSource(); // Subscribe to Topic ... consumer.Subscribe(topic); // Consuming ... while (!cancellationToken.IsCancellationRequested) { try { // Consuming Topic ... await consumer.Consume(async (consumeResult, cancellationToken) => { // Check Pausing ... if (!paused) { // Logging Message Recieved ... if (enableLogging) { // Encrypt Message ... var expirationOffset = DateTimeOffset.UtcNow .AddMinutes(encryptionLifetimeInMinute); var encryptedMessage = cryptographyService.Encrypt( text: consumeResult?.Message.Value ?? string.Empty, expirationOffset: expirationOffset ); logger .LogInformation( "Consume Recieved Message from Kafka Topic: {Topic}, Offset: {Offset}, Partition: {Partition}, Message: {Message}", topic, consumeResult?.Offset, consumeResult?.Partition, encryptedMessage ); } // Produce Message Model ... var model = consumeResult .FillConsumeResult() ?? throw RpkKafkaExceptionHelper .GetInvalidKafkaConsumeResultException(); // Check Sender and Actions Owning ... var isOwned = model.IsOwned( allowedActions, allowedSenders ); if (isOwned) { // Notify Extended Classes for Consume Message ... await ConsumeAsync( model, cancellationToken ); } // Commit Offset after Consuming ... consumer?.Commit(consumeResult); } }, cts.Token); } catch (ConsumeException ex) { // Log Exception ... if (enableLogging) { logger.LogError( ex, "Kafka Message Consume Failed: {Error}", ex.Error.Reason ); } await Task.Delay(1000, cancellationToken); } catch (Exception ex) { // Log Exception ... if (enableLogging) { logger.LogError( ex, "Exception: {Exc}", ex.Message ); } await Task.Delay(1000, cancellationToken); } } } /// /// Dispose Implementation ... /// public override void Dispose() { // consumer.Close(); consumer.Dispose(); GC.SuppressFinalize(this); base.Dispose(); } } }