using Microsoft.Extensions.Logging; using Dapr.Client; using MaksIT.Results; using MaksIT.Core.Extensions; namespace MaksIT.Dapr.Services; /// /// Publishes events to a Dapr pub/sub component. /// public interface IDaprPubSubService { /// /// Publishes to on . /// Task PublishEventAsync( string pubsubName, string topicName, object payload, IReadOnlyDictionary? metadata = null, CancellationToken cancellationToken = default); /// /// Publishes a raw byte payload. /// Task PublishByteEventAsync( string pubsubName, string topicName, ReadOnlyMemory data, string contentType = "application/json", IReadOnlyDictionary? metadata = null, CancellationToken cancellationToken = default); /// /// Publishes multiple events; returns the Dapr bulk response (including failed entries). /// Task>> BulkPublishEventAsync( string pubsubName, string topicName, IReadOnlyList events, Dictionary? metadata = null, CancellationToken cancellationToken = default); } /// /// Default using . /// public class DaprPubSubService : IDaprPubSubService { private const string ErrorMessage = "MaksIT.Dapr - Pub/sub error"; private readonly DaprClient _client; private readonly ILogger _logger; /// /// Creates a pub/sub facade backed by . /// public DaprPubSubService(ILogger logger, DaprClient client) { _logger = logger; _client = client; } /// public async Task PublishEventAsync( string pubsubName, string topicName, object payload, IReadOnlyDictionary? metadata = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pubsubName) || string.IsNullOrWhiteSpace(topicName)) return Result.BadRequest("pubsubName and topicName are required."); try { if (metadata is null) await _client.PublishEventAsync(pubsubName, topicName, payload, cancellationToken); else await _client.PublishEventAsync( pubsubName, topicName, payload, metadata as Dictionary ?? metadata.ToDictionary(static kv => kv.Key, static kv => kv.Value), cancellationToken); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task PublishByteEventAsync( string pubsubName, string topicName, ReadOnlyMemory data, string contentType = "application/json", IReadOnlyDictionary? metadata = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pubsubName) || string.IsNullOrWhiteSpace(topicName)) return Result.BadRequest("pubsubName and topicName are required."); try { await _client.PublishByteEventAsync( pubsubName, topicName, data, contentType, metadata as Dictionary ?? metadata?.ToDictionary(static kv => kv.Key, static kv => kv.Value), cancellationToken); return Result.Ok(); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result.InternalServerError([ErrorMessage, .. ex.ExtractMessages()]); } } /// public async Task>> BulkPublishEventAsync( string pubsubName, string topicName, IReadOnlyList events, Dictionary? metadata = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(pubsubName) || string.IsNullOrWhiteSpace(topicName)) return Result>.BadRequest(default!, "pubsubName and topicName are required."); try { var response = await _client.BulkPublishEventAsync(pubsubName, topicName, events, metadata, cancellationToken); return Result>.Ok(response); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, ErrorMessage); return Result>.InternalServerError(default!, [ErrorMessage, .. ex.ExtractMessages()]); } } }