Best practice when naming Durable Functions in C#

.NET, .NET Core, Azure Functions, Technical stuff
See comments

When you create a new Durable Function in C#, you get some code which looks like this:

[FunctionName("Function1")]
public static async Task> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var outputs = new List();

    // Replace "hello" with the name of your Durable Activity Function.
    outputs.Add(await context.CallActivityAsync("Function1_Hello", "Tokyo"));
    outputs.Add(await context.CallActivityAsync("Function1_Hello", "Seattle"));
    outputs.Add(await context.CallActivityAsync("Function1_Hello", "London"));

    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
}

[FunctionName("Function1_Hello")]
public static string SayHello([ActivityTrigger] string name, ILogger log)
{
    log.LogInformation($"Saying hello to {name}.");
    return $"Hello {name}!";
}

For brevity, I didn't include the HttpStart method here, it's not relevant to this discussion.

As you can see here, the execution of the program relies on some strings.

  • The FunctionName attribute which decorates the function SayHello uses a string as the function's identifier.
  • When the function is called by the context.CallActivityAsync method, the first argument of the method is the magic string identifying the function.

About Magic strings

The term "Magic string" in programming refers to identifiers which are stored in a string format. They are deemed "magic" because they have a specific meaning critical to the execution of the program.

One thing I learned when I was learning programming is that in general, it's not a great idea to rely on "magic strings". They are dangerous, because a typo will not be detected by the compiler (compilers don't normally check the format of string variables). So the risk of getting problems at some point is higher.

Using the nameof operator

In order to minimize the need of magic string, C# introduced the nameof operator.

This can be used on a number of identifiers in the code. For example you can consider this code:

public class MyClass
{
    public int MyProperty
    {
        get;
        set;
    }

    public void MyMethod()
    {
    }

    public void ShowNames()
    {
        Console.WriteLine(nameof(MyClass)); // prints "MyClass"
        Console.WriteLine(nameof(MyProperty)); // prints "MyProperty"
        Console.WriteLine(nameof(MyMethod)); // prints "MyMethod"
    }
}

So what's the point?

The point of the nameof operator is that it makes it easier to detect typos or changes in the code. For example, if you change the name of MyClass to MyNewClass, then the code won't compile unless you also change it inside the nameof operator.

If you use refactoring tools, for example inside of Visual Studio, you can easily rename all the instances of the MyClass identifier without having to search-and-replace.

Finally, it also makes it easier to navigate the code. If you are inside the nameof operator, place your cursor on MyClass and press F12 (in Visual Studio), this will take you directly inside the MyClass class, even if it is located in another file.

Next steps for Durable Functions

For Durable Functions, my first step was always to modify the code like this:

[FunctionName(nameof(Function1))]
public static async Task> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var outputs = new List();

    // Replace "hello" with the name of your Durable Activity Function.
    outputs.Add(await context.CallActivityAsync(nameof(SayHello), "Tokyo"));
    outputs.Add(await context.CallActivityAsync(nameof(SayHello), "Seattle"));
    outputs.Add(await context.CallActivityAsync(nameof(SayHello), "London"));

    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
}

[FunctionName(nameof(SayHello))]
public static string SayHello([ActivityTrigger] string name, ILogger log)
{
    log.LogInformation($"Saying hello to {name}.");
    return $"Hello {name}!";
}

These few changes (using nameof) minimize the usage of magic strings and make it easier for me to navigate the code.

Changing the templates

In order to make this easier for everyone, I proposed a change to the C# templates for Durable Functions which, if accepted, should promote the usage of nameof in the generated code.

Happy coding!

Laurent

Previous entry

Comments for Best practice when naming Durable Functions in C#