I'm working on a message bus class library for Redis (StackExchange.Redis) and NATS (AlterNats). The SubscribeAsync
method uses observers from System.Reactive for the callbacks.
The problem is _connection
in NatsSubscriber
has only DisposeAsync overload and I cannot really use async in Disposable.Create
because it would become async void. What should I do?
public sealed class NatsSubscriber<TKey, TMessage> : IBusSubscriber<TKey, TMessage>
{
private readonly NatsConnectionFactory _connectionFactory;
public NatsSubscriber(NatsConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public async Task<IDisposable> SubscribeAsync(TKey key, Action<TMessage> callback)
{
var subject = GetSubjectString(key);
if (subject == null)
{
throw new ArgumentNullException(nameof(key));
}
var connection = await _connectionFactory.GetConnectionAsync();
var observer = Observer.Create(callback);
await connection.SubscribeAsync<TMessage>(subject, data =>
{
observer.OnNext(data);
});
return Disposable.Create(subject, _ =>
{
// await connection.DisposeAsync();
observer.OnCompleted();
});
}
private string? GetSubjectString(TKey key)
{
return key switch
{
NatsKey natsKey => natsKey.Key,
string s => s,
_ => key?.ToString()
};
}
}
public sealed class RedisSubscriber<TKey, TMessage> : IBusSubscriber<TKey, TMessage>
{
private readonly IConnectionMultiplexer _connectionMultiplexer;
private readonly IRedisSerializer _serializer;
public RedisSubscriber(IConnectionMultiplexer connectionMultiplexer, IRedisSerializer serializer)
{
_connectionMultiplexer = connectionMultiplexer;
_serializer = serializer;
}
public async Task<IDisposable> SubscribeAsync(TKey key, Action<TMessage> callback)
{
var channel = CreateChannel(key);
var subscriber = _connectionMultiplexer.GetSubscriber();
var observer = Observer.Create(callback);
await subscriber.SubscribeAsync(channel, (_, redisValue) =>
{
observer.OnNext(_serializer.Deserialize<TMessage>(redisValue.ToString()));
}).ConfigureAwait(false);
return Disposable.Create(channel, x =>
{
subscriber.Unsubscribe(x);
observer.OnCompleted();
});
}
private RedisChannel CreateChannel(TKey key)
{
return key switch
{
string s => new RedisChannel(s, RedisChannel.PatternMode.Auto),
byte[] v => new RedisChannel(v, RedisChannel.PatternMode.Auto),
_ => new RedisChannel(_serializer.Serialize(key), RedisChannel.PatternMode.Literal)
};
}
}