Click or drag to resize

Basic workflow

.NET Temporal workflows are defined by an interface and then implemented as a class. We'll start out with a simple HelloWorld console application targeting .NET Core or .NET Framework and adding references to the Neon.Temporal and Neon.Common nuget packages.

C#
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using Neon.Common;
using Neon.Temporal;

namespace HelloWorld
{
    [WorkflowInterface(TaskList = "my-tasks")]
    public interface IHelloWorkflow : IWorkflow
    {
        [WorkflowMethod]
        Task<string> HelloAsync(string name);
    }

    public class HelloWorkflow : WorkflowBase, IHelloWorkflow
    {
        public async Task<string> HelloAsync(string name)
        {
            return await Task.FromResult($"Hello {name}!");
        }
    }

    public static class Program
    {
        public static async Task Main(string[] args)
        {
            // Connect to Temporal

            var settings = new TemporalSettings()
            {
                DefaultNamespace = "my-namespace",
                CreateNamespace  = true,
                HostPort         = "localhost:7933"
            };

            using (var client = await TemporalClient.ConnectAsync(settings))
            {
                // Register your workflow implementation to let Temporal
                // know we're open for business.

                await client.RegisterWorkflowAsync<HelloWorkflow>();
                await client.StartWorkerAsync("my-tasks");

                // Invoke your workflow.

                var stub = client.NewWorkflowStub<IHelloWorkflow>();

                Console.WriteLine(await stub.HelloAsync("Jeff"));
            }
        }
    }
}

Here we define IHelloWorkflow which defines the HelloAsync workflow method which implements your workflow. Workflow interfaces have a couple requirements:

  • Workflow interfaces need to inherit IWorkflow

  • All workflow interface methods must be async

The WorkflowInterfaceAttribute is optional but can be used to specify workflow related options. In this example, we're indicating that the workflow will be registered with Temporal in the my-tasks task list (we'll be discussing task lists in depth later). All workflow implementations must inherit WorkflowBase and also implement the workflow interface you defined.

Next, we define the HelloWorkflowclass that actually implements the workflow. In this case, the HelloAsync() workflow method simply returns a string value. We tag the method with a WorkflowMethodAttribute to indicate that it's a workflow entry point.

The example above is coded as a basic console application with the Main() method:

  1. Initializes the connection settings to connect Temporal running on the local machine, configuring my-namespace as the default namespace where your workflows and activities will be registered and run, and indicating that the default namespace should be created when it doesn't already exist (handy for testing).

  2. Establishes a connection to Temporal.

  3. Registers the workflow implementation.

  4. Starts the workflow worker. This informs Temporal that you've finished registering workflow and activity implementations and your workflow service is ready to be assigned tasks by Temporal within the my-tasks task list.

  5. Creates a type-safe stub that implements the specified workflow interface such that calling the method actually executes the workflow. The program then calls the stub's HelloAsync() method and prints the result to the console.

Running a workflow really couldn't be much simpler. Here's what's happening under the covers:

  1. The stub HelloAsync() method tells the Temporal server to start the workflow, with any arguments passed. Note that the stub method doesn't return until the the workflow completes.

  2. Temporal receives this requests and persists the details to Cassandra for reliability and then looks for a workflow service that is registered for my-tasks.

  3. Temporal will see that our sample application has registered itself and will schedule the workflow to be executed by our application.

  4. The application's TemporalClient receives the workflow execution request from Temporal, instantiates an instance of the HelloWorkflow workflow class and then calls the HelloAsync() method.

  5. HelloAsync() does its thing and returns the result string.

  6. Temporal persists the result to Cassandra, marks the workflow as complete, and then signals the pending stub HelloAsync() call that it can return with the value returned by your workflow implementation.

This may not look like much, but Temporal makes this operation inheritly reliable by:

  • Assigning the workflow to an available workflow instance, waiting for one to be ready.

  • Reassigning the workflow if the workflow service executing it is restarted or fails.

  • Ensuring that the workflow is idempotent such that steps that have already been completed won't be reexecuted due to workflow service failures.

  • Load balancing work across multiple registered worker instances.

  • Recording the history of the workflow execution along with the final result.

This example is super simple but even here, Temporal delivers substantial benefits. This would be non-trivial to do reliably with custom code without Temporal.

Note Note

This example is a bit contrived because we are implementing and invoking a workflow in the same program. Normally, folks will deploy a workflow service that runs until terminated and have workflows invoked from other applications such as console or ASP.NET applications. Note that any 64-bit Windows, Linux or OS/X .NET Core or Windows .NET Framework application can use TemporalClient to establish a connection to a Temporal cluster and invoke or manage workflows.

See Also

Reference