.NET 7 Out-Of-Process Durable Functions, The beginning - Part 1

2022, Nov 23

Are you waiting for .NET 7 Out-Of-Process (Isolated) Azure Durable Functions? This is becoming a reality soon. This is still in beta, but you can check this example to understand the key changes.

The Changes

The main change comes down to the Project file, in its TargetFramework and NuGet packages:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.10.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="0.4.1-beta" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.7.0" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
  </ItemGroup>
</Project>

The NuGet package Microsoft.Azure.Functions.Worker.Extensions.DurableTask is key here, as it introduces the TaskOrchestrationContext, the context used on Orchestrator Functions. This package is not compatible with Durable Functions for the in-process .NET worker though. It only works with the newer out-of-process .NET Isolated worker.

Have you heard about the Durable Task Framework before? This is explained on the Coding Night NZ YouTube video, but you can also check more details on the DTF Github.

Azure Durable Functions are an evolution of the Durable Task Framework, and with this package, it becomes very clear that the isolated (out-of-process) implementation heavily relies on that. It is worth mentioning that this is still in beta, so don't go crazy on it, as several features aren't yet available:

  • Durable Entities are not yet supported.
  • APIs for calling HTTP endpoints are not yet available.
  • Several instance management APIs are not yet implemented.

The Example

This is a simple chaining pattern example, to demo the Isolated Durable Function. If you are not familiar with the Chaining pattern, check this article.

In this article, I'm not explaining the major components of Azure Durable Functions, but you can get more details about it in this article, or through the Coding Night NZ YouTube video.

This is a simple demo where the Starter exposes the HTTPTrigger endpoint, which calls the Orchestrator that in a chain Say Hello to the cities.

Starter Function (aka DurableClient)

[Function(nameof(StarterAsync))]
public async Task<HttpResponseData> StarterAsync(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req,
    [DurableClient] DurableClientContext durableContext)
{
    string instanceId = await durableContext.Client.ScheduleNewOrchestrationInstanceAsync(nameof(HelloCitiesOrchestrator));
    _logger.LogInformation("Created new orchestration with instance ID = {instanceId}", instanceId);

    return durableContext.CreateCheckStatusResponse(req, instanceId);
}

This is an HTTPTrigger function that can be triggered with this CURL request:

curl --location --request GET 'http://localhost:7091/api/StarterAsync'

Orchestrator Function

[Function(nameof(HelloCitiesOrchestrator))]
public async Task<string> HelloCitiesOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
    string result = "";
    result += await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Tokyo") + " ";
    result += await context.CallActivityAsync<string>(nameof(SayHelloActivity), "London") + " ";
    result += await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Seattle");
    return result;
}

Activity Function

[Function(nameof(SayHelloActivity))]
public string SayHelloActivity([ActivityTrigger] string cityName)
{
    _logger.LogInformation("Saying hello to {name}", cityName);
    return $"Hello, {cityName}!";
}

Saying hello is presented on output:

Output

Check the PlayGoKids repository for this article demo.

Full Series

.NET 7 Out-Of-Process Durable Functions, The beginning - Part 1

.NET 7 Out-Of-Process Durable Functions, The beginning - Part 2