Temporal .NET – Deterministic Workflow Authoring in .NET
1–10 of 51 posts
Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#2So it's great for any code that you want to ensure reliably runs, but having methods that can't fail also opens up new possibilities, like you can:
- Write a method that implements a subscription, charging a card and sleeping for 30 days in a loop. The `await Workflow.DelayAsync(TimeSpan.FromDays(30))` is transparently translated into a persisted timer that will continue executing the method when it goes off, and in the meantime doesn't consume resources beyond the timer record in the database.
- Store data in variables instead of a database, because you can trust that the variables will be accurate for the duration of the method execution, and execution spans server restarts!
- Write methods that last indefinitely and model an entity, like a customer, that maintains their loyalty program points in an instance variable. (Workflows can receive RPCs called Signals and Queries for sending data to the method ("User just made a purchase for $30, so please add 300 loyalty points") and getting data out from the method ("What's the user's points total?").
- Write a saga that maintains consistency across services / data stores without manually setting up choreography or orchestration, with a simple try/catch statement. (A workflow method is like automatic orchestration.)
Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#3What would be the downside of writing it such as:
`ExecuteActivityAsync(e => e.DoPurchaseAsync, etc...)`?
Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#4I didn't really understand the part about 'What Problem Does 'Ref' Solve?' What would be the downside of writing it such as: `ExecuteActivityAsync (e => e.DoPurchaseAsync, etc...)`?
Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#5Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#6Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#7For those not familiar with workflows as code, a workflow is a method that is executed in a way that can't fail—each step the program takes is persisted, so that if execution is interrupted (the process crashes or machine loses power), execution will be continued on a new machine, from the same step, with all local/instance variables, threads, and the call stack intact. It also transparently retries network requests…
Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#8Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#9I didn't really understand the part about 'What Problem Does 'Ref' Solve?' What would be the downside of writing it such as: `ExecuteActivityAsync (e => e.DoPurchaseAsync, etc...)`?
That's basically what is happening here, except you create `e` ahead of time instead of a do-anything-you-want lambda. But they essentially the same thing, though I think `ExecuteActivityAsync(MyActivities.Ref.DoPurchaseAsync, etc...)` is clearer that you are referencing an activity. And since it's just a delegate that an attribute is on, you can write a helper to do what you have instead.
Re: Temporal .NET – Deterministic Workflow Authoring in .NET
#10I didn't really understand the part about 'What Problem Does 'Ref' Solve?' What would be the downside of writing it such as: `ExecuteActivityAsync (e => e.DoPurchaseAsync, etc...)`?
That's basically what is happening here, except you create `e` ahead of time instead of a do-anything-you-want lambda. But they essentially the same thing, though I think `ExecuteActivityAsync(MyActivities.Ref.DoPurchaseAsync, etc...)` is clearer that you are referencing an activity. And since it's just a delegate that an attribute is on, you can write a helper to do what you have instead.
'We solve this problem by allowing users to create instances of the class/interface without invoking anything on it. For classes this is done via FormatterServices.GetUninitializedObject and for interfaces this is done via Castle Dynamic Proxy. This lets us "reference" (hence the name Ref) methods on the objects without actually instantiating them with side effects. Method calls should never be made on these objects (and most wouldn't work anyways).'
Which sounds like a lot of heavy lifting. It seems something like
public class WorkflowBuilder where T : class
{
// Somehow workflow gets the real instance
private T Instance;
public async Task ExecuteActivityAsync(Func> func) => await func(Instance);
public async Task ExecuteActivityAsync(Func> func) => await func();
public async Task ExecuteActivityAsync(Func func) => await func();
public async Task ExecuteActivityAsync(Func func) => await func(Instance);
}
public record Purchase(string ItemID, string UserID);
public class PurchaseActivities
{
public static WorkflowBuilder OneClickBuyWorkflow => new WorkflowBuilder();
public async Task DoPurchaseAsync(Purchase purchase)
{
await OneClickBuyWorkflow.ExecuteActivityAsync(e => e.DoPurchaseAsync(purchase));
}
public static async Task DoPurchaseAsyncStatic(Purchase purchase)
{
await OneClickBuyWorkflow.ExecuteActivityAsync(() => DoPurchaseAsyncStatic(purchase));
}
}
Would pretty much achieve the same thing.