Azure / Azure/azure-functions-dotnet-worker

Service Bus message settlement from middleware when using Poco types in the functions

Open
#2,157 7 comments 5 reactions 0 assignees View on GitHub
Needs: Attention :wave:
Dominant language
C#
Stars
466
Forks
215
Avg merge
3d 10h
Merged PRs (30d)
7

Description

### Scenario
We have 100's of C# functions that consumes messages from Azure Service Bus. When there is an exception in the function logic we want to add some properties to the message.

In the in-process world we do it like this:

```
public class OurMessageProcessor : MessageProcessor
{
protected override Task CompleteProcessingMessageAsync(ServiceBusMessageActions actions, ServiceBusReceivedMessage message, FunctionResult result, CancellationToken cancellationToken)
{
// removed code...

if (!result.Succeeded)
{
Exception exception = result.Exception.InnerException ?? result.Exception;

var properties = new Dictionary
{
{ "ExceptionMessage", exception.Message }
// some more properties
};

actions.AbandonMessageAsync(message, properties);
}

// removed code
}
}
```

The logic above is packaged as an Azure Functions Extension library that all in-process function apps can consume.

### Problem
We are looking into migrating all of our Function apps to the new isolated worker. According to https://github.com/Azure/azure-functions-dotnet-worker/issues/226#issuecomment-1771932718 we can now get a reference to ServiceBusMessageActions in the isolated world.
But since we have lots of functions we want to do it via some middleware. According to https://github.com/Azure/azure-functions-dotnet-worker/issues/1824#issuecomment-1785960034 there are some hacks that let you access both ServiceBusMessageActions and ServiceBusReceivedMessage from middleware.

However, this part:

```
BindingMetadata meta = context.FunctionDefinition.InputBindings.FirstOrDefault(b => b.Value.Type == "serviceBusTrigger").Value;
var input = await context.BindInputAsync(meta);
```
...only works if your function actually consumes the message as ServiceBusReceivedMessage. In our case we consume the messages as POCO types in the function signatures. When I try to run the code above from middleware I get this exception:

`Unable to cast object of type 'a.b.c.OurPocoType' to type 'Azure.Messaging.ServiceBus.ServiceBusReceivedMessage'`

### Proposed solution
**Alternative 1**
The obvious solution would be to make it possible to retrieve ServiceBusReceivedMessage from middleware even if the called function consumed the message as a Poco type. Then we could use ServiceBusReceivedMessage in combination with ServiceBusMessageActions and call actions.AbandonMessageAsync(message, properties) just like before.

**Alternative 2**
According to the source code for ServiceBusMessageActions.AbandonMessageAsync, it basically does this:

```
public virtual async Task AbandonMessageAsync(ServiceBusReceivedMessage message, IDictionary? propertiesToModify = default, CancellationToken cancellationToken = default)
{
var request = new AbandonRequest()
{
Locktoken = message.LockToken,
};

if (propertiesToModify != null)
{
request.PropertiesToModify = ConvertToByteString(propertiesToModify);
}

await _settlement.AbandonAsync(request, cancellationToken: cancellationToken);
}
```

All that is needed from the ServiceBusReceivedMessage seem to be the LockToken. And the LockToken is already available in middleware via context.BindingContext.BindingData["LockToken"]

So maybe we are "crossing the river to get water" (as we say in Sweden) when we are struggling to inflate a ServiceBusReceivedMessage just to get something that we already have.
Maybe an easier approach would be an overload for ServiceBusMessageActions.AbandonMessageAsync like this:

```
async Task AbandonMessageAsync(string lockToken, IDictionary? propertiesToModify = default,
CancellationToken cancellationToken = default)
```

Feel free to choose the approach, but we hesitate to migrate to the isolated worker until this has a (hopefully not-so-hacky) solution.

Contributor guide

No contributing guide indexed for this repository

Research direction

Review the middleware binding path through context.BindingContext.BindingData and the ServiceBusMessageActions.AbandonMessageAsync implementation shown in the issue. Compare behavior when the function parameter is a POCO versus ServiceBusReceivedMessage, then define and validate a supported way to abandon the message with modified properties using the available lock token.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, csharp
Domain
backend, cloud
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.