-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Publisher TopicAddress customizer #1625
base: main
Are you sure you want to change the base?
Changes from all commits
ec8fab8
ef5fd10
ca7e7ca
a97c41b
4d926b7
b29c501
cd9c3da
66fbb49
7ea5ec7
9f8f4b4
5d03bf4
11e2acc
8e54601
3393a55
572e09c
3d20846
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
|
||
internal static class SemaphoreSlimExtensions | ||
{ | ||
public static async Task<IDisposable> WaitScopedAsync(this SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken) | ||
{ | ||
await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false); | ||
return new LockLifetime(semaphoreSlim); | ||
} | ||
|
||
private sealed class LockLifetime(SemaphoreSlim lockSemaphore) : IDisposable | ||
{ | ||
public void Dispose() | ||
{ | ||
lockSemaphore.Release(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
using System.Collections.Concurrent; | ||
using JustSaying.Messaging; | ||
using JustSaying.Messaging.Interrogation; | ||
using JustSaying.Models; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace JustSaying.Fluent; | ||
|
||
internal sealed class DynamicAddressMessagePublisher( | ||
string topicArnTemplate, | ||
Func<string, Message, string> topicAddressCustomizer, | ||
Func<string, StaticAddressPublicationConfiguration> staticConfigBuilder, | ||
ILoggerFactory loggerFactory) : IMessagePublisher, IMessageBatchPublisher | ||
{ | ||
private readonly string _topicArnTemplate = topicArnTemplate; | ||
private readonly ConcurrentDictionary<string, IMessagePublisher> _publisherCache = new(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think that would express what we're trying to do in a nicer way, and make it less error prone to make changes to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact, we could go one further and have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think that would be an improvement |
||
private readonly ConcurrentDictionary<string, IMessageBatchPublisher> _batchPublisherCache = new(); | ||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _topicCreationLocks = new(); | ||
martincostello marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private readonly ILogger<DynamicMessagePublisher> _logger = loggerFactory.CreateLogger<DynamicMessagePublisher>(); | ||
private readonly Func<string, Message, string> _topicAddressCustomizer = topicAddressCustomizer; | ||
private readonly Func<string, StaticAddressPublicationConfiguration> _staticConfigBuilder = staticConfigBuilder; | ||
|
||
/// <inheritdoc/> | ||
public InterrogationResult Interrogate() | ||
{ | ||
var publishers = _publisherCache.Keys.OrderBy(x => x).ToDictionary(x => x, x => _publisherCache[x].Interrogate()); | ||
var batchPublishers = _batchPublisherCache.Keys.OrderBy(x => x).ToDictionary(x => x, x => _batchPublisherCache[x].Interrogate()); | ||
|
||
return new InterrogationResult(new | ||
{ | ||
Publishers = publishers, | ||
BatchPublishers = batchPublishers, | ||
}); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public Task StartAsync(CancellationToken stoppingToken) => Task.CompletedTask; | ||
|
||
/// <inheritdoc/> | ||
public async Task PublishAsync(Message message, PublishMetadata metadata, CancellationToken cancellationToken) | ||
{ | ||
string topicArn = _topicAddressCustomizer(_topicArnTemplate, message); | ||
IMessagePublisher publisher; | ||
if (_publisherCache.TryGetValue(topicArn, out publisher)) | ||
{ | ||
await publisher.PublishAsync(message, metadata, cancellationToken).ConfigureAwait(false); | ||
return; | ||
} | ||
|
||
var lockObj = _topicCreationLocks.GetOrAdd(topicArn, _ => new SemaphoreSlim(1, 1)); | ||
|
||
_logger.LogDebug("Publisher for topic {TopicArn} not found, waiting on setup lock", topicArn); | ||
using (await lockObj.WaitScopedAsync(cancellationToken).ConfigureAwait(false)) | ||
{ | ||
if (_publisherCache.TryGetValue(topicArn, out publisher)) | ||
{ | ||
_logger.LogDebug("Lock re-entrancy detected, returning existing publisher"); | ||
await publisher.PublishAsync(message, metadata, cancellationToken).ConfigureAwait(false); | ||
return; | ||
} | ||
|
||
_logger.LogDebug("Lock acquired to configure topic {TopicArn}", topicArn); | ||
var config = _staticConfigBuilder(topicArn); | ||
|
||
_ = _publisherCache.TryAdd(topicArn, config.Publisher); | ||
publisher = config.Publisher; | ||
} | ||
|
||
_logger.LogDebug("Publishing message on newly configured topic {TopicArn}", topicArn); | ||
await publisher.PublishAsync(message, metadata, cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public Task PublishAsync(Message message, CancellationToken cancellationToken) | ||
=> PublishAsync(message, null, cancellationToken); | ||
|
||
/// <inheritdoc/> | ||
public async Task PublishAsync(IEnumerable<Message> messages, PublishBatchMetadata metadata, CancellationToken cancellationToken) | ||
{ | ||
var publisherTask = new List<Task>(); | ||
foreach (var groupByType in messages.GroupBy(x => x.GetType())) | ||
{ | ||
foreach (var groupByTopic in groupByType.GroupBy(x => _topicAddressCustomizer(_topicArnTemplate, x))) | ||
{ | ||
string topicArn = groupByTopic.Key; | ||
var batch = groupByTopic.ToList(); | ||
IMessageBatchPublisher publisher; | ||
if (_batchPublisherCache.TryGetValue(topicArn, out publisher)) | ||
{ | ||
publisherTask.Add(publisher.PublishAsync(batch, metadata, cancellationToken)); | ||
continue; | ||
} | ||
|
||
var lockObj = _topicCreationLocks.GetOrAdd(topicArn, _ => new SemaphoreSlim(1, 1)); | ||
_logger.LogDebug("Publisher for topic {TopicArn} not found, waiting on creation lock", topicArn); | ||
using (await lockObj.WaitScopedAsync(cancellationToken).ConfigureAwait(false)) | ||
{ | ||
if (_batchPublisherCache.TryGetValue(topicArn, out publisher)) | ||
{ | ||
_logger.LogDebug("Lock re-entrancy detected, returning existing publisher"); | ||
publisherTask.Add(publisher.PublishAsync(batch, metadata, cancellationToken)); | ||
continue; | ||
} | ||
|
||
_logger.LogDebug("Lock acquired to configure topic {TopicArn}", topicArn); | ||
var config = _staticConfigBuilder(topicArn); | ||
|
||
publisher = _batchPublisherCache.GetOrAdd(topicArn, config.BatchPublisher); | ||
} | ||
|
||
_logger.LogDebug("Publishing message on newly created topic {TopicName}", topicArn); | ||
publisherTask.Add(publisher.PublishAsync(batch, metadata, cancellationToken)); | ||
} | ||
} | ||
|
||
await Task.WhenAll(publisherTask).ConfigureAwait(false); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using JustSaying.Messaging; | ||
using JustSaying.Models; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace JustSaying.Fluent; | ||
|
||
internal sealed class DynamicAddressPublicationConfiguration( | ||
IMessagePublisher publisher, | ||
IMessageBatchPublisher batchPublisher) : ITopicAddressPublisher | ||
{ | ||
public IMessagePublisher Publisher { get; } = publisher; | ||
public IMessageBatchPublisher BatchPublisher { get; } = batchPublisher; | ||
|
||
public static DynamicAddressPublicationConfiguration Build<T>( | ||
string topicArnTemplate, | ||
Func<string, Message, string> topicNameCustomizer, | ||
Func<string, StaticAddressPublicationConfiguration> staticConfigBuilder, | ||
ILoggerFactory loggerFactory) | ||
{ | ||
var publisher = new DynamicAddressMessagePublisher(topicArnTemplate, topicNameCustomizer, staticConfigBuilder, loggerFactory); | ||
|
||
return new DynamicAddressPublicationConfiguration(publisher, publisher); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using JustSaying.Messaging; | ||
|
||
namespace JustSaying.Fluent; | ||
|
||
internal interface ITopicAddressPublisher | ||
{ | ||
IMessagePublisher Publisher { get; } | ||
IMessageBatchPublisher BatchPublisher { get; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using Amazon; | ||
using JustSaying.AwsTools; | ||
using JustSaying.AwsTools.MessageHandling; | ||
using JustSaying.Messaging; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace JustSaying.Fluent; | ||
|
||
internal sealed class StaticAddressPublicationConfiguration( | ||
IMessagePublisher publisher, | ||
IMessageBatchPublisher batchPublisher) : ITopicAddressPublisher | ||
{ | ||
public IMessagePublisher Publisher { get; } = publisher; | ||
public IMessageBatchPublisher BatchPublisher { get; } = batchPublisher; | ||
|
||
public static StaticAddressPublicationConfiguration Build<T>( | ||
string topicAddress, | ||
IAwsClientFactory clientFactory, | ||
ILoggerFactory loggerFactory, | ||
JustSayingBus bus) | ||
{ | ||
var topicArn = Arn.Parse(topicAddress); | ||
|
||
var eventPublisher = new SnsMessagePublisher( | ||
topicAddress, | ||
clientFactory.GetSnsClient(RegionEndpoint.GetBySystemName(topicArn.Region)), | ||
bus.SerializationRegister, | ||
loggerFactory, | ||
bus.Config.MessageSubjectProvider) | ||
{ | ||
MessageResponseLogger = bus.Config.MessageResponseLogger, | ||
MessageBatchResponseLogger = bus.PublishBatchConfiguration?.MessageBatchResponseLogger | ||
}; | ||
|
||
return new StaticAddressPublicationConfiguration(eventPublisher, eventPublisher); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.Release(1)
was redundant, we can just.Release()