Workflow Core DSL for JSON and YAML

Zaki Mohammed Zaki Mohammed
Aug 13, 2022 | 3 min read | 2807 Views | Comments

Workflow Core comes with many features one of which is its common DSL for JSON and YAML. In this article, we will explore JSON/YAML way of creating the process workflows.

If you are following along, we have already seen how to get started with Workflow Core with .NET Core (6.0); you can check Workflow Core Getting Started article. In continuation to that, we will understand how to use common DSL (JSON/YAML) provided by Workflow Core and re-create the same example with DSL instead of C# based workflow.

Check out the Workflow Core documentation and also explore the Git Repository, thanks to Daniel Gerlag.

Initial Workflow Core Setup

The initial setup of Workflow Core remains the same as explained in the previous article; except we need to add one more package for DSL:

dotnet add package WorkflowCore.DSL

The remaining steps will remain as is:

  1. Create Steps
  2. Create Workflow
  3. Register and start the Workflow from the main program file

The folder structure too remains the same:

workflow-start
|-- Workflows
    |-- ProcessPayment
        |-- Steps
            |-- ApplyDiscount.cs
            |-- ApplyShipping.cs
            |-- Finalize.cs
            |-- Initialize.cs
        |-- ProcessPaymentWorkflow.cs
        |-- ProcessPaymentWorkflow.json
        |-- ProcessPaymentWorkflow.yml
|-- GlobalUsings.cs
|-- Program.cs
|-- workflow-start.csproj

Here, we have added JSON and YAML files for the workflow. But we need to revisit step 2 since our definition for the workflow will be written in DSL (JSON/YAML).

Define DSL for Workflow

Consider the below workflow that we have created in C# ..\Workflows\ProcessPayment\ProcessPaymentWorkflow.cs

Workflows/ProcessPayment/ProcessPaymentWorkflow.cs

public class ProcessPaymentWorkflow : IWorkflow
{
    public string Id => "ProcessPaymentWorkflow";

    public int Version => 1;

    public void Build(IWorkflowBuilder<object> builder)
    {
        builder
            .UseDefaultErrorBehavior(WorkflowErrorHandling.Suspend)
            .StartWith<Initialize>()
            .Then<ApplyDiscount>()
            .Then<ApplyShipping>()
            .Then<Finalize>();
    }
}

We will comply with this and create the same definition in YAML.

Id: ProcessPaymentWorkflow
Version: 1
Steps:
  - Id: Initialize
    StepType: Initialize, workflow-start-dsl
    NextStepId: ApplyDiscount
  - Id: ApplyDiscount
    StepType: ApplyDiscount, workflow-start-dsl
    NextStepId: ApplyShipping
  - Id: ApplyShipping
    StepType: ApplyShipping, workflow-start-dsl
    NextStepId: Finalize
  - Id: Finalize
    StepType: Finalize, workflow-start-dsl

Let us zoom in to individual step definition:

- Id: Initialize
    StepType: Initialize, workflow-start-dsl
    NextStepId: ApplyDiscount

It includes the Id of the step, this can be given anything within the YML definition file. Then we have StepType which needs to match the step class name as is along with the project assembly name (workflow-start-dsl). Then we have NextStepId which points to the next step within the YML definition file; this must match the Ids given to each step within the YML definition file.

The same goes for JSON files too, even you can use simple YML to JSON conversion if you want to have both of the versions, as shown below:

{
    "Id": "ProcessPaymentWorkflow",
    "Version": 1,
    "Steps": [
        {
            "Id": "Initialize",
            "StepType": "Initialize, workflow-start-dsl",
            "NextStepId": "ApplyDiscount"
        },
        {
            "Id": "ApplyDiscount",
            "StepType": "ApplyDiscount, workflow-start-dsl",
            "NextStepId": "ApplyShipping"
        },
        {
            "Id": "ApplyShipping",
            "StepType": "ApplyShipping, workflow-start-dsl",
            "NextStepId": "Finalize"
        },
        {
            "Id": "Finalize",
            "StepType": "Finalize, workflow-start-dsl"
        }
    ]
}

This is the same except for the ugly curly braces.

Load Definition

For setting up the workflow we have to do the below steps in Program.cs file:

Add the Workflow DSL service after the Workflow server in the ServiceCollection object.

var serviceProvider = new ServiceCollection()
    .AddLogging()
    .AddWorkflow()
    .AddWorkflowDSL()
    .BuildServiceProvider();

Create a load definition method to load the workflow definition from DSL file:

static void LoadDefinition(IServiceProvider serviceProvider)
{
    var type = Deserializers.Yaml;
    var file = File.ReadAllText($"Workflows/ProcessPayment/ProcessPaymentWorkflow.{(type == Deserializers.Yaml ? "yml" : "json")}");

    var loader = serviceProvider.GetService();
    if (loader == null)
        throw new Exception("Loader not initialized");

    loader.LoadDefinition(file, type);
}

Call the LoadDefinition method instead of calling the RegisterWorkflow method of the host object:

var host = serviceProvider.GetService();
if (host == null)
    throw new Exception("Host not initialized");

LoadDefinition(serviceProvider);

host.Start();

host.StartWorkflow("ProcessPaymentWorkflow");

Console.ReadLine();

host.Stop();

Now, when you run the project it will simply print these steps console text on you console window:

dotnet run
Initialize
ApplyDiscount
ApplyShipping
Finalize

We will continue to explore some of the other Workflow Core features in upcoming posts. Stay tuned!


Zaki Mohammed
Zaki Mohammed
Learner, developer, coder and an exceptional omelet lover. Knows how to flip arrays or omelet or arrays of omelet.