.NET Workflow Engines

Max Fedotov
ITNEXT
Published in
5 min readFeb 2, 2021

--

This is a .NET specific continuation of my article Workflow Engine is a tool for you. If you do not know what Workflow Engine is or why you would use one, please refer to the previous article. I will repeat only the definitions here:

An algorithm that should be reliably executed is called a Workflow. A single step or statement of a workflow is called an Activity. A standalone application or an embedded framework responsible for reliably executing workflows is called a Workflow Engine.

Workflow Engine ensures the reliability of workflows by persisting their state and automatically re-executing the last failed activity until the whole workflow is completed.

In this article I will

  1. Provide a classification of .Net Workflow Engines in terms of how workflows can be expressed
  2. Select the best .NET Workflow Engine for developers at the moment of writing

I had to analyze the .NET Workflow Engines available on the market and I found that all of them fall pretty neatly into one of three categories.

1. XML/UI Designer

These types of Workflow Engines were created as if with the idea that business people would like to define workflows themselves (because who wouldn’t want to be a programmer). Here’s a couple of examples from Windows Workflow Foundation:

The problem is (at least in my experience) — business people prefer to delegate programming to software engineers for some reason. And for software engineers, creating imperative algorithms (workflows) using declarative languages, or using designers is not at all the optimal way. And that’s to say nothing of debugging and unit testing.

One redeeming quality of this type of Workflow Engines is that you could basically have a DSL as XML, and thus limit what is allowed in your workflows. But I think that XML is still a poor choice — if you need a DSL maybe use some real programming language instead.

You can take a look at the nice interactive demo of workflowengine.io if you want another example, but I’m going to look for something more developer-friendly.

2. Workflow Builder

A straightforward way to define a workflow in C# would be to use some kind of a builder:

This example is taken from Workflow Core. A builder is a good match for the problem because you need some way to define your activities (workflow steps) and to persist workflow state between activities.

The problem is — when using a builder to define workflows you cannot use the standard control flow statements (if-else, for, etc). You would have to use builder methods instead. For a more realistic example take a look at the so-called Simple Document Approval Workflow of Elsa workflows. I'm not saying anything bad about Elsa — it is simply a fact that even relatively simple workflows become pretty verbose and unwieldy when using this approach.

So, can we do better?

3. Native Workflows

It turns out, that with some clever tricks workflows can be “hidden” behind native language constructs.

This example is from Neon.Cadence — a C# client library for Uber’s Cadence workflow engine. The IEmailActivity methods are just plain C# methods, nothing special there. The magic happens in activity stubs which will execute activities and persist their results after an activity is successfully executed.

Workflow Engines should be able to restore any workflow state, if for example a workflow had to wait for a long time and was unloaded from memory or if a Workflow Engine node fails. How would we restore a workflow state if all we have is the results of individual activities? The only way to do it is by replaying the workflow from the beginning and using previously saved activity execution results.

This is the main drawback of these types of workflows. You have to take special care when writing a workflow to make sure that it is completely deterministic (for example Guid.NewGuid or DateTime.Now would have to be encapsulated in activities and not used directly in workflows). Further, you cannot create infinite cycles in workflows because that would mean infinite replay time (there are workarounds for that).

But even with all the drawbacks, the tradeoff is worth it for me. The convenience of writing, reading, debugging, and testing a workflow using my native tools cannot be overstated.

The Best .NET Workflow Engine?

Clearly, the best .Net Workflow Engine for me would be in that last category. I’m aware of only 2 such Workflow Engines.

Cadence (and its next iteration Temporal) has many things going for it, including a nice web UI for observability. At the same time the .NET client Neon.Cadence is created by third-party developers and it does not allow for unit testing yet, which is a deal-breaker for me.

Microsoft’s Durable Task Framework unexpectedly takes the crown. For a long time I have ignored this Workflow Engine and I blame Microsoft for that (sorry Microsoft people).

The reason I ignored Durable Task Framework is its apparent lack of strong typization of workflows and activities. In every example provided by official documentation workflows and activities are invoked using typeof instead of using some strongly typed interface. The main readme.md of the github repository suggests that the documentation for Azure Durable Functions (powered by Durable Task Framework) could be of help, but its examples don't even use types - they use plain strings to call activities. Nice. Very maintainable.

Somehow I stumbled across one example in the whole Durable Task Framework repository that shows how activities can actually be invoked using a strongly typed interface. Seek, and ye shall find. With that in mind, I believe that implementing a strongly typed wrapper to invoke workflows should not be a problem.

Durable Task Framework has many nice features, which I will not have time to describe here. While I think that it is the best .NET Workflow Engine for me, this certainly could be not the case for you. For example, supported out of the box persistence stores are limited to Azure options which may very well not suit you (although according to Microsoft creating a binding to any persistence store should not be a problem as the only requirement is some very basic key-value store capabilities).

You should of course choose the Workflow Engine that best suits your requirements and the problems you are trying to solve. Hopefully, this little guide provided you with some information to help you get started.

--

--