Now Reading
Introducing Temporal .NET – Deterministic Workflow Authoring in .NET

Introducing Temporal .NET – Deterministic Workflow Authoring in .NET

2023-05-03 11:18:51

dotnet-banner

A Temporal workflow is code that’s executed in a sturdy, dependable, and scalable method. As we speak Temporal means that you can
write workflows in Go, Java, Python, TypeScript, and extra. Now you can add .NET to that checklist with the brand new alpha launch
of the .NET SDK. Whereas this put up will deal with C#, any .NET language will work.

Totally different language runtimes have completely different trade-offs for writing workflows. Go could be very quick and useful resource environment friendly due
to runtime-supported coroutines, however that comes on the expense of kind security (even generics as applied in Go are
restricted for this use). Java can also be very quick and sort protected, however a bit much less useful resource environment friendly because of the lack of
runtime-supported coroutines (however digital threads are coming). It’d sound bizarre to say, however our dynamic languages of
JS/TypeScript and Python are in all probability essentially the most type-safe SDKs when used correctly; nevertheless, as may be anticipated, they’re
not essentially the most useful resource environment friendly. .NET supplies the perfect of all worlds: excessive efficiency like Go/Java, good useful resource
utilization like Go, and top quality type-safe APIs.

This put up will give a high-level overview of the .NET SDK and a few fascinating challenges encountered throughout its
growth. To get extra information in regards to the SDK, see:

⚠️Warning – As of this writing the .NET SDK remains to be in alpha, so APIs are topic to vary. Nonetheless the SDK is
function full, so beta and GA are coming quickly. We actually want consumer suggestions to get to these phases. Please give us
optimistic or damaging suggestions in #dotnet-sdk on Slack or on
the forums. Any suggestions helps!

Contents:

Introduction to Temporal with C#

To present a fast walkthrough of Temporal .NET, we’ll implement a simplified type of one-click shopping for in C# the place a
buy is began after which, until cancelled, can be carried out in 10 seconds.

Implementing an Exercise

Actions are the one strategy to work together with exterior assets in Temporal, corresponding to making an HTTP request or accessing
the file system. In .NET, all actions are simply delegates that are often simply strategies with the [Activity]
attribute. This is an exercise that performs a purchase order:

namespace MyNamespace;

utilizing System.Web;
utilizing System.Web.Http;
utilizing System.Web.Http.Json;
utilizing Temporalio.Actions;
utilizing Temporalio.Exceptions;

public file Buy(string ItemID, string UserID);

public class PurchaseActivities
{
    // See "Why the 'Ref' Sample" under for an evidence of this
    public static readonly PurchaseActivities Ref = ActivityRefs.Create<PurchaseActivities>();

    non-public readonly HttpClient shopper = new();

    [Activity]
    public async Process DoPurchaseAsync(Buy buy)
    {
        utilizing var resp = await shopper.PostAsJsonAsync(
          "https://api.instance.com/buy",
          buy,
          ActivityExecutionContext.Present.CancellationToken);

        // Be certain we succeeded
        strive
        {
            resp.EnsureSuccessStatusCode();
        }
        catch (HttpRequestException e) when (resp.StatusCode < HttpStatusCode.InternalServerError)
        {
            // We do not wish to retry 4xx standing codes, solely 5xx standing codes
            throw new ApplicationFailureException("API returned error", e, nonRetryable: true);
        }
    }
}

This exercise makes an HTTP name and takes care to not retry some sorts of HTTP errors. Word the Ref property on the
high. That is used for exercise callers to have the ability to reference strategies. See the Why the ‘Ref’ Pattern part under.
If we used a static methodology we’d not want this.

Implementing a Workflow

Now that we now have an exercise, we will implement our workflow:

namespace MyNamespace;

utilizing Temporalio.Workflows;

public enum PurchaseStatus
{
    Pending,
    Confirmed,
    Cancelled,
    Accomplished
}

[Workflow]
public class OneClickBuyWorkflow
{
    // See "Why the 'Ref' Sample" under for an evidence of this
    public static readonly OneClickBuyWorkflow Ref = WorkflowRefs.Create<OneClickBuyWorkflow>();

    non-public PurchaseStatus currentStatus = PurchaseStatus.Pending;
    non-public Buy? currentPurchase;

    [WorkflowRun]
    public async Process<PurchaseStatus> RunAsync(Buy buy)
    {
        currentPurchase = buy;

        // Give consumer 10 seconds to cancel or replace earlier than we ship it by
        strive
        {
            await Workflow.DelayAsync(TimeSpan.FromSeconds(10));
        }
        catch (TaskCanceledException)
        {
            currentStatus = PurchaseStatus.Cancelled;
            return currentStatus;
        }

        // Replace the standing, carry out the acquisition, replace the standing once more
        currentStatus = PurchaseStatus.Confirmed;
        await Workflow.ExecuteActivityAsync(
            PurchaseActivities.Ref.DoPurchaseAsync,
            currentPurchase!,
            new() { ScheduleToCloseTimeout = TimeSpan.FromMinutes(2) });
        currentStatus = PurchaseStatus.Accomplished;
        return currentStatus;
    }

    [WorkflowSignal]
    public async Process UpdatePurchaseAsync(Buy buy) => currentPurchase = buy;

    [WorkflowQuery]
    public PurchaseStatus CurrentStatus() => currentStatus;
}

Workflows have to be deterministic, and we use a customized activity scheduler (defined later on this put up). Additionally notice that this
has the identical kind of Ref property because the exercise for a similar causes.

Discover the Workflow.DelayAsync name there? That may be a sturdy Temporal timer. When a cancellation token isn’t offered
to it, it defaults to Workflow.CancellationToken in order that cancelling the workflow implicitly cancels the duties being
awaited. Workflows should use Temporal-defined timing and scheduling, so one thing like Process.DelayAsync can’t be used.
See the Workflow Determinism part under for extra particulars.

Working a Employee

Workflows and actions are run in employees like so:

utilizing MyNamespace;
utilizing Temporalio.Consumer;
utilizing Temporalio.Employee;

// Create a shopper to localhost on "default" namespace
var shopper = await TemporalClient.ConnectAsync(new("localhost:7233"));

// Cancellation token to close down employee on ctrl+c
utilizing var tokenSource = new CancellationTokenSource();
Console.CancelKeyPress += (_, eventArgs) =>
{
    tokenSource.Cancel();
    eventArgs.Cancel = true;
};

// Create an exercise occasion since we now have occasion actions. If we had
// all static actions, we might simply reference these instantly.
var actions = new PurchaseActivities();

// Create employee with the exercise and workflow registered
utilizing var employee = new TemporalWorker(
    shopper,
    new(taskQueue: "my-task-queue")
    {
        Actions = { actions.DoPurchaseAsync },
        Workflows = { typeof(OneClickBuyWorkflow) },
    });

// Run employee till cancelled
Console.WriteLine("Working employee");
strive
{
    await employee.ExecuteAsync(tokenSource.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Employee cancelled");
}

When executed, the employee will pay attention for Temporal server requests to carry out workflow and exercise invocations.

Executing a Workflow

utilizing MyNamespace;
utilizing Temporalio.Consumer;

// Create a shopper to localhost on "default" namespace
var shopper = await TemporalClient.ConnectAsync(new("localhost:7233"));

// Begin a workflow
var deal with = await shopper.StartWorkflowAsync(
    OneClickBuyWorkflow.Ref.RunAsync,
    new Buy(ItemID: "item1", UserID: "user1"),
    new(id: "my-workflow-id", taskQueue: "my-task-queue"));

// We are able to replace the acquisition if we would like
await deal with.SignalAsync(
    OneClickBuyWorkflow.Ref.UpdatePurchaseAsync,
    new Buy(ItemID: "item2", UserID: "user1"));

// We are able to cancel it if we would like
await deal with.CancelAsync();

// We are able to question its standing, even when the workflow is full
var standing = await deal with.QueryAsync(OneClickBuyWorkflow.Ref.CurrentStatus);
Console.WriteLine("Buy workflow standing: {0}", standing);

// We are able to additionally wait on the end result (which for our instance is identical as question)
standing = await deal with.GetResultAsync();
Console.WriteLine("Buy workflow end result: {0}", standing);

This can be a tiny style of the various options supplied by Temporal .NET. See the
.NET SDK README for extra particulars.

Why the ‘Ref’ Sample?

Within the code samples above, it’s possible you’ll discover the Ref properties which can be created by way of
ActivityRefs.Create<PurchaseActivities>() and WorkflowRefs.Create<OneClickBuyWorkflow>(). We are going to clarify why they
exist and what options had been thought-about.

What Drawback Does ‘Ref’ Resolve?

When you might want to invoke an exercise from a workflow, or a workflow/sign/question/and many others from a shopper, you might want to know
which one you wish to name. Every of the requires invoking these objects has an overload that accepts a string title,
which works tremendous however doesn’t present kind security for the return kind and/or parameter.

For kind security, we wish to settle for a generically typed delegate we will guarantee kind security on. Say we wish to settle for a
Func<T, TResult> as an exercise. If it is a static methodology, no drawback — MyClass.MyStaticMethod is simply tremendous to move
for that kind. But when it is an occasion methodology, there’s a drawback. MyClass.MyInstanceMethod doesn’t work as a result of C#
doesn’t will let you reference an occasion methodology with out the occasion.

In Java you could possibly have MyClass::myInstanceMethod, or in Go you could possibly have MyReceiver.MyInstanceMethod, or in Python
you could possibly have MyClass.my_instance_method, and they’ll all work by accepting the occasion as the primary parameter.
However not C#, so we have to work round this.

How Does ‘Ref’ Work?

We clear up this drawback by permitting customers to create cases of the category/interface with out invoking something on it. For
lessons that is accomplished by way of
FormatterServices.GetUninitializedObject
and for interfaces that is accomplished by way of Castle Dynamic Proxy. This
lets us “reference” (therefore the title Ref) strategies on the objects with out really instantiating them with unintended effects.
Technique calls ought to by no means be made on these objects (and most would not work anyhow).

So now you may have public static readonly MyClass Ref = ActivityRefs.Create<MyClass>() on the high of MyClass. Then
you should utilize Workflow.ExecuteActivityAsync(MyClass.Ref.MyInstanceMethod, "some-param", ... and know that if
MyInstanceMethod took an int parameter as a substitute, you’d get a compile-time error.

Because of this, we suggest all exercise lessons with occasion strategies have a Ref and all workflow lessons have a
Ref.

What Different Options Exist?

There are a few different options to the reference-instance-method-for-type-safety drawback.

See Also

The commonest method is to make use of the dynamic proxy sample. That is what Sturdy Entities, Orleans, and our personal
Temporal Java SDK use. This creates dynamic proxies of interfaces that you simply then invoke the strategies on, which in flip
do the distant invocation. Whereas that is kind protected for parameters and return sorts, this has drawbacks listed under:

  • You need to conform to the sync/async nature of the strategies. Which signifies that since we assist synchronous actions,
    you’d invoke them by way of a proxy as synchronous, which isn’t legitimate since they’re asynchronous. We would must disallow
    synchronous actions for this use case, which we do not wish to do. Equally, we would must make queries async which is
    additionally undesirable as a result of it would encourage side-effecting calls in queries.
  • It’s not straightforward to move choices on the name web site. While you invoke the proxied methodology, there is no such thing as a parameter to present
    choices. For some Temporal calls like invoking an exercise, we require no less than one possibility. You find yourself having to
    transfer the choices for a name away from the decision which is dangerous for authors and readers.
  • It’s not apparent you’re making an exterior name. When invoking an exercise or related, it can be crucial that it’s
    apparent you might be invoking an exercise. If accomplished on a proxied object, it’s going to appear to be every other invocation.
  • You can not differentiate a number of makes use of of the identical methodology. For instance, in Temporal you could have a name for simply beginning
    the workflow and a name for executing the workflow and ready on its end result. The proxy would solely be for executing,
    so that you’d nonetheless want some strategy to reference the run methodology for simply beginning a workflow.
  • This requires interfaces or no less than lessons with digital strategies. Dynamic proxying in .NET can not proxy non-virtual
    strategies. We do not wish to require interfaces/digital.

One other method that is turning into extra frequent nowadays is supply turbines. That is one thing we hope to completely
assist, we simply haven’t constructed it but (see the Future part under).

How It Works – Workflow Determinism

In Temporal, workflows have to be deterministic. This implies along with disallowing all the apparent stuff like random
and system time, Temporal should even have strict management over activity scheduling and coroutines in an effort to guarantee
deterministic execution.

Whereas Python and others enable full management over the occasion loop (see
this blog post), .NET doesn’t. We make a customized
TaskScheduler to order all created duties deterministically, however we can not management timers and plenty of Process administration
calls in .NET (e.g. easy overloads of Process.Run) use TaskScheduler.Default implicitly as a substitute of the popular
TaskScheduler.Present. Even
some analyzer rules
discourage use of calls that implicitly use TaskScheduler.Present although that’s precisely what must be utilized in
workflows. Typically it isn’t even apparent that one thing inner will
use the default scheduler unexpectedly.

With the intention to clear up this and forestall different non-deterministic calls, we’d run in a sandbox. However latest variations of .NET
have accomplished away with a few of this tooling (particularly “Code Entry Safety” and “Partially Trusted Code” options).
These identical points additionally seem in Temporal Go and Java SDKs the place we ask customers to not do any platform threading/async
exterior of our deterministic scheduler.

So, we ask customers to ensure all activity calls are accomplished on the present activity scheduler and to not use timers. See the
.NET SDK README for extra particulars on what we restrict.

We discovered it so onerous to know which calls use threading and system timers in .NET that we are attempting to eagerly detect
these conditions at runtime and compile time. At runtime, by default, we allow a tracing
EventListener that intercepts
a choose few info-level activity occasions to examine whether or not, if we’re working in a workflow, all duties are being carried out on
the right scheduler. Technically this occasion listener listens for all of those particular activity occasions no matter
whether or not a workflow is executing, however our examine to ignore non-workflow occasions could be very low-cost (mainly only a thread
native examine). However we do enable the listener to be disabled if wanted. This listener will droop the workflow (i.e. fail
the “workflow activity”) when invalid activity scheduling is encountered. The workflow will resume when code is deployed with a
repair.

Sooner or later, there are two issues that may assist right here. First, we wish to create analyzers to seek out these errors at
compile time (see “Future” part under). Second for timers, the
new TimeProvider API not too long ago merged will enable trendy .NET variations
to allow us to management timer creation as a substitute of falling again to system timers.

Way forward for the .NET SDK

The .NET SDK is already a full-featured SDK on par with the others. Its alpha label is because of immaturity, not
incompleteness. Throughout this era, APIs usually are not assured to stay appropriate. There are three issues we wish to add
within the close to time period (although no ensures they are going to be added earlier than GA).

First, we wish to add supply technology. We’ve got the form of actions and workflows, and subsequently we will generate
idiomatic caller-side buildings to make invocation safer/simpler. Supply technology will at all times be optionally available, however could
turn out to be the popular strategy to name actions and workflows.

Second, we wish to create a set of analyzers. We all know what you may and might’t name in a workflow, so static evaluation to
catch these invalid calls must be pretty straightforward to develop. This could work like every other .NET analyzer and is
one thing we wish to develop quickly. Then once more, possibly our approaches/investments in AI to seek out Temporal workflow errors
can be accomplished first ????.

Lastly, the new TimeProvider API will enable us to intercept timers
in a way more clear method for customers. Granted, it is going to solely work on the latest .NET variations.

The .NET SDK can be supported like different SDKs. Subsequently, Temporal options like workflow
updates can be added to the .NET SDK as they’re added to different SDKs.

Strive it out as we speak! We wish all suggestions, optimistic or damaging! Be a part of us in #dotnet-sdk on Slack or
on the forums.

Source Link

What's Your Reaction?
Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top