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(req, instanceId);
}
[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(entityId, nameof(CounterEntity.Add), 10);
int currentValue = await context.Entities.CallEntityAsync<int>(entityId, nameof(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 |
Configure Durable Functions with the Netherite storage provider
Happy Coding.