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()]);
}
}
}