Live data from Hacker News

Temporal .NET – Deterministic Workflow Authoring in .NET

temporal.io

1–10 of 51 posts

Re: Temporal .NET – Deterministic Workflow Authoring in .NET

#2
For 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 that fail.

So 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

#4
post #3

I 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

#7
post #2

For 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…

What happens if you have a workflow in progress that you want to change the implementation of? Eg. If I had a workflow that was waiting for 30 days but then decided I wanted that interval to be 7 days instead?

Re: Temporal .NET – Deterministic Workflow Authoring in .NET

#9
post #4
post #3

I 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.

If you're worried about someone doing something other than calling a method in the lambda it's pretty straightforward to use an Expression> and validate the contents of the expression. Seems like that would make for a much better dev experience than this static Ref property pattern that you don't see anywhere else in C#.

Re: Temporal .NET – Deterministic Workflow Authoring in .NET

#10
post #4
post #3

I 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.

The main reason I'm curious is because they write

'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.
Post reply on HN