Skip to content
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

It is unnecessarily hard to send messages from recoverability #474

Open
danielmarbach opened this issue Mar 12, 2024 · 0 comments
Open

It is unnecessarily hard to send messages from recoverability #474

danielmarbach opened this issue Mar 12, 2024 · 0 comments
Labels

Comments

@danielmarbach
Copy link
Contributor

Describe the feature.

Functions counterpart to Particular/NServiceBus#6973

Is your feature related to a problem? Please describe.

With function the whole story becomes even more complex. Because we do not bind IMessageSession and IFunctionEndpoint require access to FunctionContext the user needs to go through all sorts of hurdles to be able to send a message within recoverability. The code looks more or less like that

public interface IFunctionContextAccessor
{
    FunctionContext? FunctionContext { get; set; }
}

internal sealed class FunctionContextAccessor : IFunctionContextAccessor
{
    private AsyncLocal<FunctionContextHolder> currentContext = new();

    public FunctionContext? FunctionContext
    {
        get => currentContext.Value?.FunctionContext;
        set
        {
            var holder = currentContext.Value;
            if (holder != null)
            {
                holder.FunctionContext = null;
            }

            if (value != null)
            {
                currentContext.Value = new FunctionContextHolder { FunctionContext = value };
            }
        }
    }

    private class FunctionContextHolder
    {
        public FunctionContext? FunctionContext;
    }
}
public sealed class FunctionContextAccessorMiddleware : IFunctionsWorkerMiddleware
{
    private IFunctionContextAccessor FunctionContextAccessor { get; }

    public FunctionContextAccessorMiddleware(IFunctionContextAccessor accessor)
    {
        FunctionContextAccessor = accessor;
    }

    public Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        if (FunctionContextAccessor.FunctionContext != null)
        {
            throw new InvalidOperationException($"Unable to initalize {nameof(IFunctionContextAccessor)}: context has already been initialized.");
        }

        FunctionContextAccessor.FunctionContext = context;

        return next(context);
    }
}
class RecoverabilityBehavior(IFunctionEndpoint functionEndpoint, IFunctionContextAccessor contextAccessor, ILogger<RecoverabilityBehavior> logger) : Behavior<IRecoverabilityContext>
{
    public override async Task Invoke(IRecoverabilityContext context, Func<Task> next)
    {
        if (context.RecoverabilityAction is MoveToError && context.Metadata.TryGetValue("NServiceBus.ExceptionInfo.Data.Message type", out var messageType) && messageType.EndsWith(nameof(MyMessage)))
        {
            logger.LogInformation("MyMessage failed. Publishing MyMessageFailed event.");
            var message = new MyMessageFailed();
            await functionEndpoint.Publish(message, contextAccessor.FunctionContext, context.CancellationToken);
        }

        await next();
    }
}
        var host = new HostBuilder()
            .ConfigureServices(services =>
            {
                services.AddSingleton<RecoverabilityBehavior>();
                services.AddSingleton<IFunctionContextAccessor, FunctionContextAccessor>();
            })
            .ConfigureFunctionsWorkerDefaults(app =>
            {
                app.UseMiddleware<FunctionContextAccessorMiddleware>();
            })
            .UseNServiceBus(endpointConfiguration =>
            {          
                endpointConfiguration.AdvancedConfiguration.Pipeline.Register(p => p.GetRequiredService<RecoverabilityBehavior>(), "Publishes an event in certain cases.");
            })
            .Build();

Describe the requested feature

Plus now the Core approach is no longer interoperable with the functions approach due to the lack of a common way to send messages.

Describe alternatives you've considered

Additional Context

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant