-
-
Notifications
You must be signed in to change notification settings - Fork 244
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
Working on message bus subscriptions options #278
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -553,6 +553,7 @@ public virtual async Task WillWaitForItemAsync() { | |
return; | ||
|
||
try { | ||
Log.MinimumLevel = LogLevel.Trace; | ||
await queue.DeleteQueueAsync(); | ||
await AssertEmptyQueueAsync(queue); | ||
|
||
|
@@ -564,14 +565,16 @@ public virtual async Task WillWaitForItemAsync() { | |
Assert.InRange(sw.Elapsed, TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(5000)); | ||
|
||
_ = Task.Run(async () => { | ||
await SystemClock.SleepAsync(500); | ||
await Task.Delay(500); | ||
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.
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. Yes I know that, but in this case we never want the sleep to be virtual. |
||
_logger.LogInformation("Enqueuing async message"); | ||
await queue.EnqueueAsync(new SimpleWorkItem { | ||
Data = "Hello" | ||
}); | ||
}); | ||
|
||
sw.Restart(); | ||
workItem = await queue.DequeueAsync(TimeSpan.FromSeconds(1)); | ||
_logger.LogInformation("Dequeuing message with timeout"); | ||
workItem = await queue.DequeueAsync(TimeSpan.FromSeconds(10)); | ||
sw.Stop(); | ||
if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Time {Elapsed:g}", sw.Elapsed); | ||
Assert.True(sw.Elapsed > TimeSpan.FromMilliseconds(400)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,68 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Foundatio.Messaging { | ||
public interface IMessageSubscriber { | ||
Task SubscribeAsync<T>(Func<T, CancellationToken, Task> handler, CancellationToken cancellationToken = default) where T : class; | ||
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. Wondering if we should create extension method overloads for this that just set a token on options. Will users know there is an implicit conversion happening here for cancellation token? 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. Maybe, I am worried about us having overload conflicts since there are already a bunch of overloads for this method. I pretty much did this just for compatibility. |
||
Task<IMessageSubscription> SubscribeAsync<T>(Func<T, CancellationToken, Task> handler, MessageSubscriptionOptions options = null, CancellationToken cancellationToken = default) where T : class; | ||
} | ||
|
||
public static class MessageBusExtensions { | ||
public static Task SubscribeAsync<T>(this IMessageSubscriber subscriber, Func<T, Task> handler, CancellationToken cancellationToken = default) where T : class { | ||
return subscriber.SubscribeAsync<T>((msg, token) => handler(msg), cancellationToken); | ||
public static Task<IMessageSubscription> SubscribeAsync<T>(this IMessageSubscriber subscriber, Func<T, Task> handler, MessageSubscriptionOptions options = null, CancellationToken cancellationToken = default) where T : class { | ||
return subscriber.SubscribeAsync<T>((msg, token) => handler(msg), options, cancellationToken); | ||
} | ||
|
||
public static Task SubscribeAsync<T>(this IMessageSubscriber subscriber, Action<T> handler, CancellationToken cancellationToken = default) where T : class { | ||
public static Task<IMessageSubscription> SubscribeAsync<T>(this IMessageSubscriber subscriber, Action<T> handler, MessageSubscriptionOptions options = null, CancellationToken cancellationToken = default) where T : class { | ||
return subscriber.SubscribeAsync<T>((msg, token) => { | ||
handler(msg); | ||
return Task.CompletedTask; | ||
}, cancellationToken); | ||
}, options, cancellationToken); | ||
} | ||
|
||
public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func<IMessage, CancellationToken, Task> handler, CancellationToken cancellationToken = default) { | ||
return subscriber.SubscribeAsync<IMessage>((msg, token) => handler(msg, token), cancellationToken); | ||
public static Task<IMessageSubscription> SubscribeAsync(this IMessageSubscriber subscriber, Func<IMessage, CancellationToken, Task> handler, MessageSubscriptionOptions options = null, CancellationToken cancellationToken = default) { | ||
return subscriber.SubscribeAsync<IMessage>((msg, token) => handler(msg, token), options, cancellationToken); | ||
} | ||
|
||
public static Task SubscribeAsync(this IMessageSubscriber subscriber, Func<IMessage, Task> handler, CancellationToken cancellationToken = default) { | ||
return subscriber.SubscribeAsync((msg, token) => handler(msg), cancellationToken); | ||
public static Task<IMessageSubscription> SubscribeAsync(this IMessageSubscriber subscriber, Func<IMessage, Task> handler, MessageSubscriptionOptions options = null, CancellationToken cancellationToken = default) { | ||
return subscriber.SubscribeAsync((msg, token) => handler(msg), options, cancellationToken); | ||
} | ||
|
||
public static Task SubscribeAsync(this IMessageSubscriber subscriber, Action<IMessage> handler, CancellationToken cancellationToken = default) { | ||
public static Task<IMessageSubscription> SubscribeAsync(this IMessageSubscriber subscriber, Action<IMessage> handler, MessageSubscriptionOptions options = null, CancellationToken cancellationToken = default) { | ||
return subscriber.SubscribeAsync((msg, token) => { | ||
handler(msg); | ||
return Task.CompletedTask; | ||
}, cancellationToken); | ||
}, options, cancellationToken); | ||
} | ||
} | ||
|
||
public interface IMessageSubscription : IAsyncDisposable { | ||
string SubscriptionId { get; } | ||
IDictionary<string, string> Properties { get; } | ||
} | ||
|
||
public class MessageSubscription : IMessageSubscription { | ||
private readonly Func<ValueTask> _disposeSubscriptionFunc; | ||
|
||
public MessageSubscription(string subscriptionId, Func<ValueTask> disposeSubscriptionFunc) { | ||
SubscriptionId = subscriptionId; | ||
_disposeSubscriptionFunc = disposeSubscriptionFunc; | ||
} | ||
|
||
public string SubscriptionId { get; } | ||
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); | ||
|
||
public ValueTask DisposeAsync() { | ||
return _disposeSubscriptionFunc?.Invoke() ?? new ValueTask(); | ||
} | ||
} | ||
|
||
public class MessageSubscriptionOptions { | ||
/// <summary> | ||
/// The topic name | ||
/// </summary> | ||
public string Topic { get; set; } | ||
|
||
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,58 @@ | ||
using System; | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Foundatio.Messaging { | ||
public class SharedMessageBusOptions : SharedOptions { | ||
/// <summary> | ||
/// The topic name | ||
/// The default topic name | ||
/// </summary> | ||
public string Topic { get; set; } = "messages"; | ||
public string DefaultTopic { get; set; } = "messages"; | ||
|
||
/// <summary> | ||
/// Controls which types messages are mapped to. | ||
/// Resolves message types | ||
/// </summary> | ||
public IMessageRouter Router { get; set; } | ||
|
||
/// <summary> | ||
/// Statically configured message type mappings. <see cref="Router"/> will be run first and then this dictionary will be checked. | ||
/// </summary> | ||
public Dictionary<string, Type> MessageTypeMappings { get; set; } = new Dictionary<string, Type>(); | ||
} | ||
|
||
public interface IMessageRouter { | ||
// get topic from bus options, message and message options | ||
// get message type from message and options | ||
// get .net type from topic, message type and properties (headers) | ||
IConsumeMessageContext ToMessageType(Type messageType); | ||
Type ToClrType(IConsumeMessageContext context); | ||
} | ||
|
||
public interface IConsumeMessageContext { | ||
string Topic { get; set; } | ||
string MessageType { get; set; } | ||
IDictionary<string, string> Properties { get; } | ||
Comment on lines
+32
to
+33
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. Would this work for kafka? I'm dealing with 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. Well it can't be kafka specific. It has access to properties which should include the headers from the kafka message. |
||
} | ||
|
||
public class ConsumeMessageContext : IConsumeMessageContext { | ||
public string Topic { get; set; } | ||
public string MessageType { get; set; } | ||
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); | ||
} | ||
|
||
public class SharedMessageBusOptionsBuilder<TOptions, TBuilder> : SharedOptionsBuilder<TOptions, TBuilder> | ||
where TOptions : SharedMessageBusOptions, new() | ||
where TBuilder : SharedMessageBusOptionsBuilder<TOptions, TBuilder> { | ||
public TBuilder Topic(string topic) { | ||
if (string.IsNullOrEmpty(topic)) | ||
throw new ArgumentNullException(nameof(topic)); | ||
Target.Topic = topic; | ||
Target.DefaultTopic = topic; | ||
return (TBuilder)this; | ||
} | ||
|
||
public TBuilder MessageTypeResolver(IMessageRouter resolver) { | ||
if (resolver == null) | ||
throw new ArgumentNullException(nameof(resolver)); | ||
Target.Router = resolver; | ||
return (TBuilder)this; | ||
} | ||
|
||
|
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.
probably should revert this and leave this up to the caller.