using Microsoft.Extensions.Hosting; using Svrnty.Sample.Events; using Svrnty.CQRS.Events.Abstractions.Subscriptions; using Svrnty.CQRS.Events.Abstractions.EventStore; using Microsoft.Extensions.Logging; using Svrnty.CQRS.Events.Abstractions; using System; using System.Threading; using System.Threading.Tasks; using Svrnty.Sample.Workflows; namespace Svrnty.Sample.BackgroundServices; /// /// Background service that demonstrates consuming events from event streams. /// /// /// /// Phase 1.4 Event Consumption: /// This service shows how to consume events using . /// It subscribes to the "user-analytics" subscription (broadcast mode) and logs all events. /// /// /// Usage Patterns: /// - Broadcast mode: All consumer instances receive all events (great for logging/analytics) /// - Exclusive mode: Only one consumer receives each event (great for work distribution) /// /// /// Example: /// This consumer demonstrates the broadcast pattern where multiple services can /// independently observe and react to events without affecting each other. /// /// public class EventConsumerBackgroundService : BackgroundService { private readonly IEventSubscriptionClient _subscriptionClient; private readonly ILogger _logger; public EventConsumerBackgroundService( IEventSubscriptionClient subscriptionClient, ILogger logger) { _subscriptionClient = subscriptionClient; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Event consumer starting..."); // Wait a bit for the application to fully start await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken); _logger.LogInformation("Subscribing to 'user-analytics' subscription (broadcast mode)..."); var consumerId = $"analytics-{Guid.NewGuid():N}"; try { // Subscribe to the user-analytics subscription // This is a broadcast subscription, so multiple consumers can receive the same events await foreach (var @event in _subscriptionClient.SubscribeAsync( "user-analytics", consumerId, stoppingToken)) { // Process the event await ProcessEventAsync(@event, stoppingToken); } } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { _logger.LogInformation("Event consumer stopping gracefully..."); } catch (Exception ex) { _logger.LogError(ex, "Error in event consumer"); } } private Task ProcessEventAsync(ICorrelatedEvent @event, CancellationToken cancellationToken) { // Log the event details _logger.LogInformation( "[ANALYTICS] Received event: {EventType} (EventId: {EventId}, CorrelationId: {CorrelationId}, OccurredAt: {OccurredAt})", @event.GetType().Name, @event.EventId, @event.CorrelationId, @event.OccurredAt); // Type-specific processing switch (@event) { case UserAddedEvent userAdded: _logger.LogInformation( "[ANALYTICS] User added: UserId={UserId}, Name={Name}", userAdded.UserId, userAdded.Name); break; case UserRemovedEvent userRemoved: _logger.LogInformation( "[ANALYTICS] User removed: UserId={UserId}", userRemoved.UserId); break; case UserInvitedEvent userInvited: _logger.LogInformation( "[ANALYTICS] User invited: InvitationId={InvitationId}, Email={Email}, Inviter={Inviter}", userInvited.InvitationId, userInvited.Email, userInvited.InviterName); break; case UserInviteAcceptedEvent inviteAccepted: _logger.LogInformation( "[ANALYTICS] Invitation accepted: InvitationId={InvitationId}, UserId={UserId}, Name={Name}", inviteAccepted.InvitationId, inviteAccepted.UserId, inviteAccepted.Name); break; case UserInviteDeclinedEvent inviteDeclined: _logger.LogInformation( "[ANALYTICS] Invitation declined: InvitationId={InvitationId}, Reason={Reason}", inviteDeclined.InvitationId, inviteDeclined.Reason ?? "(no reason provided)"); break; default: _logger.LogInformation( "[ANALYTICS] Unknown event type: {EventType}", @event.GetType().Name); break; } // In a real application, you might: // - Send metrics to a monitoring system // - Update analytics dashboards // - Trigger notifications // - Store audit logs return Task.CompletedTask; } }