77 lines
2.8 KiB
C#
77 lines
2.8 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Reflection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Svrnty.CQRS.Notifications.Abstractions;
|
|
|
|
namespace Svrnty.CQRS.Notifications.Grpc;
|
|
|
|
/// <summary>
|
|
/// Publishes notifications to subscribed gRPC clients.
|
|
/// </summary>
|
|
public class NotificationPublisher : INotificationPublisher
|
|
{
|
|
private readonly NotificationSubscriptionManager _manager;
|
|
private readonly ILogger<NotificationPublisher> _logger;
|
|
|
|
// Cache subscription key property info per notification type
|
|
private static readonly ConcurrentDictionary<Type, SubscriptionKeyInfo> _keyCache = new();
|
|
|
|
public NotificationPublisher(
|
|
NotificationSubscriptionManager manager,
|
|
ILogger<NotificationPublisher> logger)
|
|
{
|
|
_manager = manager;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task PublishAsync<TNotification>(TNotification notification, CancellationToken ct = default)
|
|
where TNotification : class
|
|
{
|
|
ArgumentNullException.ThrowIfNull(notification);
|
|
|
|
var keyInfo = GetSubscriptionKeyInfo(typeof(TNotification));
|
|
var subscriptionKey = keyInfo.Property.GetValue(notification);
|
|
|
|
if (subscriptionKey == null)
|
|
{
|
|
_logger.LogWarning(
|
|
"Subscription key {PropertyName} is null on {NotificationType}, skipping notification",
|
|
keyInfo.PropertyName, typeof(TNotification).Name);
|
|
return;
|
|
}
|
|
|
|
_logger.LogDebug(
|
|
"Publishing {NotificationType} with subscription key {PropertyName}={KeyValue}",
|
|
typeof(TNotification).Name, keyInfo.PropertyName, subscriptionKey);
|
|
|
|
await _manager.NotifyAsync(notification, subscriptionKey, ct);
|
|
}
|
|
|
|
private static SubscriptionKeyInfo GetSubscriptionKeyInfo(Type type)
|
|
{
|
|
return _keyCache.GetOrAdd(type, t =>
|
|
{
|
|
var attr = t.GetCustomAttribute<StreamingNotificationAttribute>();
|
|
if (attr == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Type {t.Name} is not marked with [{nameof(StreamingNotificationAttribute)}]. " +
|
|
$"Add the attribute with a SubscriptionKey to enable streaming notifications.");
|
|
}
|
|
|
|
var property = t.GetProperty(attr.SubscriptionKey);
|
|
if (property == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Property '{attr.SubscriptionKey}' specified in [{nameof(StreamingNotificationAttribute)}] " +
|
|
$"was not found on type {t.Name}.");
|
|
}
|
|
|
|
return new SubscriptionKeyInfo(attr.SubscriptionKey, property);
|
|
});
|
|
}
|
|
|
|
private sealed record SubscriptionKeyInfo(string PropertyName, PropertyInfo Property);
|
|
}
|