question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Workflow Engine & API Changes

See original GitHub issue

For V3, I am thinking of making a number of changes.

These are just thoughts and I am looking for feedback. Nothing is set in stone as of yet.

Activity Data & Handlers

Currently, an activity is both a DTO as well as a service - it contains input & output properties and methods that implement behavior.

If we separate activity data from behavior, things will become easier:

  • Serialization of activities simplified
  • Ability to construct workflows by composing actual activity objects (this ties into the next section)
  • Easier testing.

It would look something like this:

public class WriteLine : Activity
{
   public IActivityInput<string> Text { get; set; }
}

public class WriteLineHandler : IActivityHandler<WriteLine>
{
   public override void ExecuteActivity(WriteLine activity)
   {
      Console.WriteLine(activity.Text);
   }
}

A workflow would be constructed as follows:

public class MyWorkflow : Workflow
{
   public MyWorkflow()
   {
      Activities.Add(
         new Sequence ( // Support for container activities with natural scope.
            new WriteLine { Text = "Line 1" }, // Set a string literal. Implicitly cast to ActivityInput<string>.
            new WriteLine { Text = new CodeInput<string>(context => context.Input) }, // Set a dynamic value using a C# lambda.
            new WriteLine { Text = new LiquidInput<string>("Hello {{ Input }}") } // Set a value using a liquid expression.
         )
      );
   }
}

Connecting Activities

With V2, all activities are connected to one another via connections. Although this is convenient, it also poses a challenge to implement nested scopes and self-scheduling of child activities such as If, ForEach, Switch and others.

It also makes setting up coded workflows inconvenient. Although the Workflow Builder API does a nice job using Then<> and When, the following workflow would be even easier to setup:


public class MyWorkflow : Workflow
{
   public MyWorkflow()
   {
      Activities.Add(
         new Sequence(
            new HttpEndpoint { Path = "/demo", Methods = new[] { "GET" } },
            new If { 
               Condition = JavaScriptInput<bool>("queryString('answer') == '42'"),
               True = new WriteLine { Text = "Correct!" },
               False = new WriteLine { Text = "Wrong..." }
            }
        );
   }
}

This also makes connecting multiple sources to the same destination much more natural:

public class MyWorkflow : Workflow
{
   public MyWorkflow()
   {
      var exit = new WriteLine { Text = "Bye!" };

      Activities.Add(
         new Sequence ( // Support for container activities with natural scope.
            new HttpEndpoint { Path = "/demo", Methods = new[] { "GET" } },
            new If { 
               Condition = JavaScriptInput<bool>("queryString('answer') == '42'"),
               True = new Sequence(WriteLine { Text = "Correct!" }, exit), // Connect to exit node.
               False = new Sequence(new WriteLine { Text = "Wrong..." }, exit) // Connect to same exit node.
            }
         )
      );
   }
}

With this API, containment would be an intrinsic concept, which removes all of the unintuitive quirks we currently have with Composite Activities in V2.

A consequence of this is that we lose the concept of “outcomes”. Instead, an activity now defines “ports”. The If activity for example would look something like this:

public class If : Activity
{
   [Port] public IActivity True { get; set; }
   [Port] public IActivity False { get; set; }
}

public class IfHandler : IActivityHandler<If>
{
   public async ValueTask ExecuteActivityAsync(If activity, ActivityExecutionContext context)
   {
      var result = await context.EvaluateAsync(activity.Condition);
   
      if (result == true)
         context.ScheduleActivity(activity.True);
      else
         context.ScheduleActivity(activity.False);
   }
}

Being able to assign activities to activity properties requires the workflow designer to receive a number of changes:

  • When connecting e.g. some WriteLine activity to the True property of an If activity, we want to see a connection drawn. The designer needs to be able to serialize that into an hierarchical object model, instead of maintaining a list of connections between activities.
  • When designing a workflow, the user should be able to freely connect one activity to another. The model however should use a Sequence container activity when the user connects activities that don’t have explicit ports like the If activity.

Unwinding

V2 currently relies on “scopes” to enable outcomes such as "Iterate" to automatically re-schedule its owning activity such as ForEach, which then determines if the loop has finished or not. If finished, it schedules the "Done" outcome.

This implementation however is prone to error and is hard to apply to custom activities because there are multiple moving parts that need to cooperate to make this work.

With the proposed API changes, this will become a thing of the past, because it now becomes easy to determine if a child activity (an activity set to a port or within a sequence (which itself is a container activity) has completed. Activities such as ForEach will have two port properties: Loop and Complete, both of which are of type IActivity.

Pseudo code for ForEach:

public class ForEach : Activity
{
   public IActivityInput<bool> Condition { get; set; }
   [Port] public IActivity Loop { get; set; }
   [Port] public IActivity Complete { get; set; }
}

public class ForEachHandler : IActivityHandler<ForEach>
{
   public async ValueTask ExecuteActivityAsync(ForEach activity, ActivityExecutionContext context)
   {
      var result = await context.EvaluateAsync(activity.Condition);
   
      if (result == true)
      {
          context.ScheduleActivity(activity.Loop)
      }
   }

   // This method is invoked by the workflow engine once an activity completes that was scheduled by this activity.
   public void ScheduledActivityCompleted(ForEach activity, ActivityExecutionContext context, IActivity completedActivity)
   {
      if(completedActivity != Loop)
         return;

        var result = await context.EvaluateAsync(activity.Condition);
     
        if (result == true)
        {
            context.ScheduleActivity(activity.Loop)
        }
        else
        {
            context.ScheduleActivity(activity.Complete)
        }
   }
}

TODO: Describe workflow bookmarks for resuming suspended workflows.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:2
  • Comments:19 (12 by maintainers)

github_iconTop GitHub Comments

5reactions
sfmskywalkercommented, Jun 20, 2022

Here’s an updated preview that takes inspiration from WF & ADF: elsa-3-designer-preview-2

5reactions
sfmskywalkercommented, Jun 17, 2022

Check out this sneak-peek of a POC I’m working on for the Elsa 3 designer with embedded activities (as discussed during last community call)! There’s still a lot of work to be done.

For example, for simple activities, we could keep it embedded as you see now, but for more complex activities such as Sequence and Flowchart, we should probably only render a symbol and a display name, but open a new “tab” with a designer to edit the contents.

I’m also not sure about rendering the embedded activities as-is. Perhaps it needs a slightly different representation. Different color, just icon, no icon at all, etc. Feedback welcome!

elsa-3-embedded-activities-preview-1

Read more comments on GitHub >

github_iconTop Results From Across the Web

Release Notes | Documentation | WorkflowEngine
8.0.0​. February 21, 2023. There are no changes to the .NET libraries. In this version, only Workflow Designer packages are updated.
Read more >
Workflow Engine APIs
The engine changes activity states in response to an API call to update the ... The Oracle Workflow Engine and Notification APIs are...
Read more >
Changes to fields in the Workflows API
We're making some changes to some of the fields used in the Workflows API. Certain optional fields will no longer be included in...
Read more >
Release Notes | Documentation
Workflow Engine has been upgraded to version 9.1.1, refer to the release notes for more information. In the Workflow API, the ability to...
Read more >
Add an API State Change Workflow
Click Tasks → API State Change. The list of API state change tasks awaiting for approval appears. · Click on Approve or Reject...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found