Thursday, June 20, 2024

.NET Isolated Azure Durable Functions: Support for Durable Entities with Netherite Storage Provider

Finally, Durable Entities are now supported with Netherite Storage Provider in .NET Isolated Azure Durable Functions.

While durabletask-netherite: 1.5.1 says it added support for isolated entities, unfortunately, it does not work (microsoft/durabletask-netherite/issues/390: System.NotSupportedException: Durable entities are not supported by the current backend configuration). We need Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite version 1.5.3 (at least) for this to work.

Now let's look at how to configure Netherite Storage Provider in a .NET Isolated Azure Durable Function.

First, we need to install the following package: 
   Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite >= 1.5.3

Then we need to update the host.json as follows.

{
  "version""2.0",
  "extensions": {
    "durableTask": {
      "storageProvider": {
        "type""Netherite"
      }
    }
  },
  "logging": {
    
...
  }
}

And finally, for local development, we need to update the local.settings.json as follows.

{
  "IsEncrypted"false,
  "Values": {
    "AzureWebJobsStorage""UseDevelopmentStorage=true",
    "EventHubsConnection""SingleHost",
    "FUNCTIONS_WORKER_RUNTIME""dotnet-isolated"
  }
}

You need to set up an Event Hubs namespace to run Netherite on Azure and I am not covering that in this post.

Now let's write a simple function to try out the Durable Entity functionality with Netherite.

I have the following simple CounterEntity.

using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask.Entities;
using Microsoft.Extensions.Logging;

namespace AzureFunctionsDemo.Netherite;

public class CounterEntity : TaskEntity<int>
{
    readonly ILogger logger;

    public CounterEntity(ILogger<CounterEntity> logger)
    {
        this.logger = logger;
    }

    public void Add(int amount) => State += amount;

    public void Reset() => State = 0;

    public int Get() => State;

    [Function(nameof(CounterEntity))]
    public Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
    {
        return dispatcher.DispatchAsync(this);
    }
}

And I am invoking CounterEntity through the following HTTP function that calls a Durable Function.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Entities;
using Microsoft.Extensions.Logging;

namespace AzureFunctionsDemo.Netherite;

public static class Function1
{
    [Function(nameof(HttpStart))]
    public static async Task<HttpResponseData> HttpStart(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get""post")] HttpRequestData req,
        [DurableClient] DurableTaskClient client,
        FunctionContext executionContext)
    {
        ILogger logger = executionContext.GetLogger(nameof(HttpStart));

        string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(EntityOrchestrator));

        logger.LogInformation("Started orchestration with ID = '{instanceId}'."instanceId);

        return await client.CreateCheckStatusResponseAsync(reqinstanceId);
    }

    [Function(nameof(EntityOrchestrator))]
    public static async Task<int> EntityOrchestrator(
        [OrchestrationTrigger] TaskOrchestrationContext context)
    {
        ILogger logger = context.CreateReplaySafeLogger(nameof(EntityOrchestrator));

        var entityId = new EntityInstanceId(nameof(CounterEntity)"myCounter");

        await context.Entities.CallEntityAsync(entityIdnameof(CounterEntity.Add), 10);

        int currentValue = await context.Entities.CallEntityAsync<int>(entityIdnameof(CounterEntity.Get));

        return currentValue;
    }
}

After triggering the HTTP function, it will execute the Orchestration invoking the Durable Entity. Once the Orchestration is completed and when I check the Orchestration Status, I can see the current value of the CounterEntity.

Orchestration Status
Great to see it's working, was waiting for this. 

Hope this helps.

More read:
   Configure Durable Functions with the Netherite storage provider

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment