-
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
[WIP] Message context #480
[WIP] Message context #480
Conversation
If others are happy with this, then before merging needs:
|
.. and it needs to be wired in
etc |
This plan effectively broadens the handler contract again, adding
Are there other options?An alternate proposal is this: JustSaying defines an interface A Handler class can constructor-inject this interface (but does not have to). And later call it to get the context as needed. Pros:
Cons:
|
Great write-up @AnthonySteele.
It would be registered as a singleton (because it's a kinda a factory), and we'd do it for them in There's also the pro that if your handler has some dependencies, you could push the As an example, maybe I'd have a class RetryHelper : IRetryHelper
{
public RetryHelper(IMessageContextAccessor contextAccessor)
{
// later on do something with contextAccessor.GetContext().AttemptNumber (or whatever) Now your handler can depend on |
That underscores that we can't do things this way unless we find out a good way to do that at all. Yes it makes sense to register |
So what I'm proposing is that we do exactly what aspnetcore does, which backs it by AsyncLocal. It's sort of an ambient context, but with a nice way to get hold of it (via a dependency injected factory). It turns out the lifetime of it would be academic, it's singleton in aspnet purely for performance (very minor). |
Thank you, I had lost that link. Sometimes code says a thousand words 😄 |
My thinking on the accessor would be something like one of the two ideas: Option A - Always presentIn this example, the accessor is always in DI and things will break without it. // Any relevant pre-processing...
var accessor = resolver.GetInstance<IMyAccessorThing>();
accessor.Current = new AwsContextThing();
// All the stuff that processes things...
accessor.Current = null; // We're done with this message completely Option B - Accessor is optionalIn this example, it's opt-in for if you need it (to save allocations etc.) like how // On setup
services.AddJustSaying(...)
.AddJusSayingContextOrWhateverForAwsContextAccessStuff();
// Any relevant pre-processing...
var accessor = resolver.GetInstance<IMyAccessorThing>();
if (accessor != null)
{
accessor.Current = new AwsContextThing();
}
// All the stuff that processes things...
if (accessor != null)
{
accessor.Current = null; // We're done with this message completely
} |
In this model Like assess to a current http context, this might lead people into bad designs - e.g. with http context, it is better that the controller has the sole responsibility for dealing with http request and responses, and classes that it calls to do domain things do not couple to this by e.g. looking up header values in the current request. However "you can use it to make bad designs" is not a fatal flaw, it is a pitfall to be wary of. |
@martincostello not sure I'm fully following you. If we go down the The implementation is static, so we wouldn't need to resolve it in order to set it. We could check to see if We could get something from DI and set the context, but only if we first create a DI scope that we manage. Perf wise it would be interesting to see how these 2 options do. Not sure it's going to be enough of an impact to influence a decision, but I'll hold judgement for when I see the numbers. Just to be completely clear, I think there are 3 high-level options:
|
What I’m suggesting is kind of 2 and 3 @slang25, I’ve just included a pattern where it’s optional because in ASP.NET Core |
Hmm, good points, paring this down to the essentials, the DI aspect is unnecessary. If the data is in async local storage, then a static helper method will be enough to get it. |
Ah yes, I'm with you now 👍 |
I like the optional thing, it seems like a nice pattern. The only downside I can think of is that if a user forgets the registration and is using |
Closed in favour of the option discussed in the comments |
The null thing was why the code example had the null checks 😉 |
You don't need a |
See #486 |
A start of an approach to the "message Envelopes" feature.
Do not merge, just please say if you think this is worth completing, or if it is misguided or you can see a better way.
This approach broadens the handler contract again into
Task<bool> HandleAsync(T message, MessageContext context)
.We have expanded the contact before for async, moving from
bool
toTask<bool>
return, with a similar wrapper.The now idea is that
IHandlerAsync.Handle<T>
as before and work with the message deserialised asT
.IHandlerWithMessageContext
instead, and get the additionalmessageContext
object containing various other properties such as the queue url and the raw SQS message.Message
base class. if you need them, that's not a "simple consumer", that's a "consumer that needs the metadata".IHandlerWithMessageContext
need be dealt with, asIHandlerAsync
instances can be wrapped in theHandlerAdapter
and appear asIHandlerWithMessageContext
.MessageContext
could make it easy to read e.g. specific named metadata on the raw SQS Message.The additional
MessageContext
object is a parallel to thePublishMetadata
in #449."Envelope" is also a possible design here, but has the additional implementation complexity that as the Envelope contains a generic message of some type
T: Message
, the envelope is also generic overT: Message
; whereas theMessageContext
is not a generic at all.