Durable Task .NET SDK

repository·main·Indexed 41 days ago

https://github.com/microsoft/durabletask-dotnet

A client-side library for implementing durable orchestrations, activities, and entities. It connects to external sidecars or managed services such as the Durable Task Scheduler and the Azure Functions .NET Isolated host. The SDK supports both function-based and class-based syntax, provides source generators for type-safe invocations via Microsoft.DurableTask.Generators, and includes support for activity versioning and OpenTelemetry distributed tracing.

Tokens
22.7K
Snippets
51
Records
88
Agent score
52%

What's inside Durable Task .NET SDK

  1. Overview of Durable Task .NET Client SDK

    main

    The Durable Task .NET SDK is a standalone library used to implement Durable Task orchestrations, activities, and entities. Unlike the Durable Task Framework (which is self-hosted with storage backends like Azure Storage or MSSQL), this SDK is designed to connect to a "sidecar" process or a managed Azure endpoint.

    Common connection targets include:

    • The Azure Functions .NET Isolated host.
    • The Durable Task Scheduler (preview managed service).
    • Other gRPC-based sidecars.
  2. Use the gRPC DurableTaskWorker implementation

    main

    The Microsoft.DurableTask.Grpc package provides a gRPC-based worker implementation designed to receive and process work from a gRPC task hub. This is used to implement a worker that can scale out and process tasks managed by a central task hub via gRPC communication.

    Key types for implementing a gRPC worker:

    • GrpcDurableTaskWorker: The core worker implementation.
    • GrpcDurableTaskWorkerOptions: Configuration options for the gRPC worker.
  3. Use ShimDurableTaskClient to interact with a task hub

    main

    The ShimDurableTaskClient provides a DurableTaskClient implementation that allows you to interact with a task hub using the Microsoft.DurableTask.Core.IOrchestrationServiceClient interface. This is useful for bridging Microsoft.DurableTask with core orchestration services.

    Commonly used types in this package:

    • ShimDurableTaskClient: The primary client implementation.
    • ShimDurableTaskClientOptions: Configuration options for the client.
  4. Implement worker-level versioning

    main

    Worker-level versioning allows you to associate a worker deployment with a specific version string. This ensures that a worker only processes orchestration instances that match its version, preventing errors during deployments of breaking changes.

    Key components of this pattern:

    • Worker Configuration: Use UseVersioning(...) with MatchStrategy = Strict to pin the worker to a specific version. The worker will reject orchestration instances stamped with a different version.
    • Client Configuration: Use UseDefaultVersion() to automatically stamp every new orchestration instance with the current default version.
    • Orchestration Logic: Access context.Version within the orchestration to determine which version was used when the instance was scheduled.

    To perform an upgrade, redeploy the worker with the new implementation and a new version string.

    // Conceptual usage pattern:
    // Worker side
    worker.UseVersioning(new VersioningOptions {
        Version = "2.0",
        MatchStrategy = MatchStrategy.Strict
    });
    
    // Client side
    client.UseDefaultVersion();
    
    // Orchestration side
    var version = context.Version;
  5. Maintain replay determinism when adding new versions

    main

    When evolving your orchestration logic, follow these rules to avoid non-determinism failures:

    • Adding a new version (e.g., V3) as a new class: This is safe and additive. The worker dispatches based on (name, version). Existing V2 instances will continue to run on the V2 class, while new instances will use V3. You can migrate V2 to V3 using ContinueAsNew(NewVersion = "3").
    • Modifying an existing version class in place: This is unsafe. If you change the code of a version (e.g., ProcessJobsWorkflowV2) while instances are mid-execution, the next replay may emit different actions than the recorded history, causing a non-determinism failure.

    The Mental Model: Treat the class you ship as a permanent contract. If the logic needs to change, ship a new class with a new [DurableTask(Version = "...")] and migrate existing instances at a ContinueAsNew boundary.

  6. Use Class-based syntax for Durable Functions

    main

    Class-based syntax allows you to define orchestrators and activities as classes instead of functions. This relies on the Microsoft.DurableTask.Generators package (currently in preview).

    When using this syntax, source generators create type-safe extension methods on the client and context objects, allowing you to call activities and orchestrations without manually specifying names or return types.

    Note: This feature is in preview and may undergo significant changes. It is recommended to use function-syntax for production workloads.

    [DurableTask(nameof(HelloCitiesTyped))]
    public class HelloCitiesTyped : TaskOrchestrator<string?, string>
    {
        public async override Task<string> RunAsync(TaskOrchestrationContext context, string? input)
        {
            string result = "";
            result += await context.CallSayHelloTypedAsync("Tokyo") + " ";
            result += await context.CallSayHelloTypedAsync("London") + " ";
            result += await context.CallSayHelloTypedAsync("Seattle");
            return result;
        }
    }
    
    [DurableTask(nameof(SayHelloTyped))]
    public class SayHelloTyped : TaskActivity<string, string>
    {
        public override Task<string> RunAsync(TaskActivityContext context, string cityName)
        {
            return Task.FromResult($"Hello, {cityName}!");
        }
    }
  7. Define strongly-typed events with DurableEventAttribute

    main

    You can define strongly-typed external events by applying the [DurableEvent] attribute to a record or class. This triggers a source generator that creates extension methods for waiting on these events within an orchestrator, providing compile-time type safety and IntelliSense support.

    To define an event, use the attribute and pass the name of the event as the argument (typically using nameof).

    [DurableEvent(nameof(ApprovalEvent))]
    public sealed record ApprovalEvent(bool Approved, string? Approver);
  8. How on-demand sandboxes work in DTS

    main

    On-demand sandboxes allow you to run activities in isolated, DTS-managed containers.

    Worker Configuration: In the main application, a worker profile class is used to declare the container image, CPU, memory, max concurrency, and the specific on-demand sandbox activity identities using options.AddActivity(name, version). It is critical that the main app and the remote worker use the same identity constants (typically defined in a shared/ project) to ensure synchronization.

    Runtime Injection: The remote worker image does not require manual configuration of DTS runtime settings. When DTS starts the sandbox, it automatically injects the following:

    • Scheduler endpoint
    • Task hub
    • Worker profile
    • Capacity
    • Sandbox provider
    • Sandbox identifier

    The worker then connects and reports the activity identities it has registered within the image.

  9. Understand the DurableTaskClient abstractions

    main

    The Microsoft.DurableTask.Client.Core package provides the fundamental abstractions for interacting with a task hub. It defines the interfaces and types used to start orchestrations, retrieve orchestration details, and send events.

    Important: This package contains only the abstractions and does not include a concrete implementation. To actually interact with a task hub, you must install and use a concrete client package, such as Microsoft.DurableTask.Client.Grpc.

  10. Migrate long-running orchestrations using ContinueAsNew

    main

    For orchestrations that cannot be terminated or re-deployed in place (because editing the class would break replay determinism), use the eternal-orchestration migration pattern.

    This pattern involves:

    1. Shipping a new version of the orchestration class (e.g., v2) alongside the existing one (v1).
    2. Using ContinueAsNew(NewVersion = "...") at a safe boundary in the v1 implementation.

    ContinueAsNew acts as a replay-safe migration boundary: the orchestration history is fully reset at this point, so the v2 implementation is not replayed against v1's history. To preserve state across the migration, pass the required data as the NewInput in ContinueAsNewOptions.

    // Inside the v1 orchestration implementation
    await Context.ContinueAsNew(new ContinueAsNewOptions 
    {
        NewVersion = "2", 
        NewInput = totalJobs 
    });
  11. Understand Durable Task Worker abstractions

    main

    The Microsoft.DurableTask.Worker.Core package provides the fundamental abstractions for building a worker that receives and processes work from a task hub.

    Important: This package contains only the interfaces and base abstractions. It does not include a concrete worker implementation. To actually run a worker, you must use a concrete implementation package, such as Microsoft.DurableTask.Worker.Grpc.

    Core types provided in this package include:

    • DurableTaskWorker: The primary abstraction for a worker.
    • DurableTaskWorkerOptions: Configuration options for the worker.
    • IDurableTaskWorkerBuilder: An interface used to configure and build a worker instance.