Skip to content

Commit

Permalink
Add chapters on Blocking Activities and Triggers
Browse files Browse the repository at this point in the history
A comprehensive explanation of blocking activities and triggers has been provided. The blocking activity chapter explains their importance in workflow designs and gives an illustrative example. The trigger chapter elaborates their specialized conditioning role in starting workflows and gives an example of how they can be used.
  • Loading branch information
sfmskywalker committed Apr 1, 2024
1 parent 61a31b8 commit 0080667
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Elsa.Workflows;

public class MyEvent : Activity
{
protected override void Execute(ActivityExecutionContext context)
{
context.CreateBookmark("MyEvent");
}
}
23 changes: 23 additions & 0 deletions writerside/snippets/extensibility/custom-activities/MyEvent2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Elsa.Extensions;
using Elsa.Workflows;

namespace Elsa.Server.Web.Activities;

public class MyEvent : Trigger
{
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
if (context.IsTriggerOfWorkflow())
{
await context.CompleteActivityAsync();
return;
}

context.CreateBookmark("MyEvent");
}

protected override object GetTriggerPayload(TriggerIndexingContext context)
{
return "MyEvent";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var bookmarkPayload = "MyEvent";
var activityTypeName = ActivityTypeNameHelper.GenerateTypeName<MyEvent>();
await _workflowRuntime.TriggerWorkflowsAsync(activityTypeName, bookmarkPayload);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
public class MyEventWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
builder.Root = new Sequence
{
Activities =
{
new WriteLine("Starting workflow..."),
new MyEvent(), // This will block further execution until the MyEvent's bookmark is resumed.
new WriteLine("Event occurred!")
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
public class MyEventWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
builder.Root = new Sequence
{
Activities =
{
new MyEvent
{
CanStartWorkflow = true // Enable this activity to start this workflow when triggered.
},
new WriteLine("Event occurred!")
}
};
}
}
67 changes: 67 additions & 0 deletions writerside/topics/Custom-Activities.topic
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,73 @@
<p>Elsa's design choice to favor service location through the execution context over constructor dependency injection is deliberate. This decision supports the flexibility in constructing activity instances directly within workflow definitions. Incorporating constructor-based DI would complicate the instantiation of activities, thereby restricting the fluidity in building and manipulating workflow graphs programmatically.</p>
</note>
</chapter>
<chapter title="Blocking Activities" id="blocking-activities">
<p>
Blocking activities represent an important concept in workflow design, enabling a workflow to pause its execution until a specified external event occurs.
Instead of completing immediately, these activities generate a bookmark—a placeholder of sorts—that allows the workflow to resume from the same point once the required conditions are met.
This mechanism is particularly useful for orchestrating asynchronous operations or waiting for external inputs.
Notable examples of blocking activities include the <code>Event</code> and <code>Delay</code> activities.
</p>
<p>
Here is an illustrative example of a blocking activity that creates a bookmark to pause its execution, awaiting an external trigger to proceed:
</p>
<code-block lang="c#" src="extensibility/custom-activities/MyEvent1.cs"/>
<p>Below, we demonstrate how to incorporate the <code>MyEvent</code> activity into a workflow:</p>
<if switcher-key="Programmatic">
<code-block lang="c#" src="extensibility/custom-activities/MyEventWorkflow1.cs"/>
</if>
<if switcher-key="Designer">
<img src="my-event-workflow-1.png" alt="MyEvent will block execution until it is triggered" border-effect="rounded" thumbnail="true"/>
</if>
<p>
Upon initiating the workflow, execution will proceed until it reaches the <code>MyEvent</code> activity.
At this juncture, the activity will pause the workflow by not completing and instead creating a bookmark, waiting for an external signal to continue.
While waiting for the external signal to be received, there is no more work for the workflow engine to do and will persist the workflow instance and remove it from memory.
</p>
<p>To effectively resume a workflow from a bookmark, the workflow runtime requires specific information:</p>
<list>
<li>The type of activity that initiated the bookmark</li>
<li>The bookmark payload, which was generated by the activity</li>
</list>
<p>Here's how to programmatically resume a workflow, utilizing the bookmark payload produced by the blocking activity, with the <code>IWorkflowRuntime</code> service:</p>
<code-block lang="c#" src="extensibility/custom-activities/MyEventResumeSnippet1.cs"/>
<p>This approach could be seamlessly integrated into an API controller to facilitate the resumption of workflows in response to external events.</p>
</chapter>
<chapter title="Triggers" id="activity-triggers">
<p>
Triggers serve as specialized activities designed to initiate workflows in reaction to specific external events, such as HTTP requests or messages from a message queue. This capability allows workflows to dynamically respond to outside stimuli, making them highly versatile in various automated processes.
</p>
<p>
To illustrate, the <code>MyEvent</code> activity, previously discussed as a blocking activity, can also be adapted to function as a trigger:
</p>
<code-block lang="c#" src="extensibility/custom-activities/MyEvent2.cs"/>
<p>
By implementing the <code>ITrigger</code> interface, or indirectly by inheriting from <code>Trigger</code> as is seen in this example,
the activity becomes a trigger, thereby enabling higher-level services such as <code>IWorkflowRuntime</code> to start workflows in response to predefined events.
This mechanism is integral to the design of reactive, event-driven workflows.
</p>
<p>
It's important to understand the nuanced behavior of trigger activities within the workflow's execution. Specifically, these activities are designed to assess whether their activation directly initiated the current execution burst. If so, instead of generating a bookmark—which would ordinarily pause the workflow awaiting an external trigger—they opt to complete immediately. This approach prevents the workflow from entering a paused state right at the start, ensuring that it only pauses when waiting for subsequent triggers.
</p>
<p>
With this mechanism in place, workflows can be dynamically initiated by events associated with trigger activities, such as the <code>MyEvent</code> activity. Let's explore an example workflow that leverages this capability:
</p>
<if switcher-key="Programmatic">
<code-block lang="c#" src="extensibility/custom-activities/MyEventWorkflow2.cs"/>
<p>In this instance, it's crucial to set the <code>CanStartWorkflow</code> property to <code>true</code> on the trigger activity. This configuration signals that the activity has the authority to commence the workflow, serving as a key component in activating workflow triggers.</p>
</if>
<if switcher-key="Designer">
<img src="my-event-workflow-2.png" alt="MyEvent used as a trigger to start the workflow" border-effect="rounded" thumbnail="true"/>
<p>In the visual designer, achieving this activation entails selecting the <ui-path>Trigger workflow</ui-path> option. This step is essential to empower the activity with the capability to automatically initiate the workflow.</p>
</if>
<p>To programmatically trigger any and all workflows with the <code>MyEvent</code> trigger, we use the exact same code as we did when resuming a bookmark using the <code>IWorkflowRuntime</code> service:</p>
<code-block lang="c#" src="extensibility/custom-activities/MyEventResumeSnippet1.cs"/>
<p>
This streamlined approach underscores the versatility of the <code>IWorkflowRuntime</code> service in managing workflow executions, whether it's initiating new workflows or resuming paused ones through bookmarks.
By leveraging a consistent method, developers can efficiently design workflows that respond dynamically to both internal and external events, further enhancing the interactivity and responsiveness of their applications.
</p>
</chapter>

</chapter>

<chapter title="Registering Activities" id="registering-activities">
Expand Down

0 comments on commit 0080667

Please sign in to comment.