Conditional branches
Tutorial 7 🔎
dependency injection - initial branch
conditional branches - final branch
conditional branches pull request - pull request with code changes
Overview​
In this tutorial we explain how conditional transitions can be organized in a process scheme. Furthermore, we describe a process example where different priorities are defined based on Actions that are executed as result of defining certain types of Conditions.
The following stages are covered:
- Writing conditions in
ActionProvider
. - Application build and run.
- Running the process.
Survey processing case​
In this case a quite simple logic for processing a survey is presented in the process scheme where the priorities for processing the queries of potential clients are calculated through defining several conditions.
Overall, a particular survey is received where the following information is identified:
- Technology stack - it means which technology is implemented by the prospective client or company.
- Amount of developers - how many developers work for the company.
- Job title - the specific designation within an organization, normally associated with a job description.
First, the technology stack is validated. When it is different to .NET, the survey is just dismissed and the process is terminated. Otherwise, the process goes on.
Next, the amount of developers is verified. It enables to determine the size of the company: Small, Medium and Big.
Then, the job position is checked. Here, the job title is evaluated by considering the departments: Development, Management and Other.
The mechanisms for defining the described Conditions
are specified in this section.
Once the Conditions are defined, the priorities for processing the surveys are calculated. These priorities will be set automatically as: High, Medium and Low.
Prerequisites​
- You should go through previous tutorials OR clone: dependency-injection branch.
- JetBrains Rider or Visual Studio.
- Command prompt or Terminal.
Backend​
The code improvements are described in this section.
ActionProvider​
The ActionProvider
must be modified. Here, several changes are included to show how Conditions can be written in the application.
First, we have a standard implementation of _syncConditions
. Besides, the realization of part of the methods IsConditionAsync
and GetConditions
which haven't been implemented previously. Furthermore, IsHighPriority
and IsMediumPriority
which
define the synchronous conditions that were added.
When the condition named "IsHighPriority"
is called, the WFE checks if this condition exists in the action provider. The GetConditions
method is used for this check.
Next, the conditions are classified as asynchronous or synchronous. In this case, we will only consider synchronous conditions. The IsConditionAsync
method is used for this check.
If a condition exists in the action provider, and it is synchronous, the ExecuteCondition
method is called. This method finds the condition by name and executes it.
After that the conditions are defined as
asynchronous or synchronous (in this case we consider only sync conditions). Next, the condition is passed to processInstance
, and
the name
is returned through ExecuteCondition
method.
Finally, either IsHighPriority
or IsMediumPriority
method will be executed. In these methods, the Report
object is obtained from
the process parameters, and the priorities are set based on the CompanySize
and Position
parameters provided earlier.
As the order of condition checking is undefined, the function IsMediumPriority
includes a call to the IsHighPriority
function to make
the workflow behavior predictable.
The object Report
is filled out with data provided in previous steps of the process. How it works is described further in this
section.
These new conditions will determinate the priorities for processing requests considering the example process presented in this tutorial. Below the resulting code.
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
using OptimaJet.Workflow.Core.Model;
using OptimaJet.Workflow.Core.Runtime;
using WorkflowApi.Hubs;
namespace WorkflowLib;
public class ActionProvider : IWorkflowActionProvider
{
private readonly Dictionary<string, Func<ProcessInstance, WorkflowRuntime, string, CancellationToken, Task>>
_asyncActions = new();
private readonly Dictionary<string, Func<ProcessInstance, WorkflowRuntime, string, bool>>
_syncConditions = new();
private IHubContext<ProcessConsoleHub> _processConsoleHub;
public ActionProvider(IHubContext<ProcessConsoleHub> processConsoleHub)
{
_processConsoleHub = processConsoleHub;
_asyncActions.Add(nameof(SendMessageToProcessConsoleAsync), SendMessageToProcessConsoleAsync);
_syncConditions.Add(nameof(IsHighPriority),IsHighPriority);
_syncConditions.Add(nameof(IsMediumPriority), IsMediumPriority);
}
public void ExecuteAction(string name, ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter)
{
throw new NotImplementedException();
}
public async Task ExecuteActionAsync(string name, ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter,
CancellationToken token)
{
if (!_asyncActions.ContainsKey(name))
{
throw new NotImplementedException($"Async Action with name {name} isn't implemented");
}
await _asyncActions[name].Invoke(processInstance, runtime, actionParameter, token);
}
public bool ExecuteCondition(string name, ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter)
{
throw new NotImplementedException();
if (!_syncConditions.ContainsKey(name))
{
throw new NotImplementedException($"Sync Condition with name {name} isn't implemented");
}
return _syncConditions[name].Invoke(processInstance, runtime, actionParameter);
}
public Task<bool> ExecuteConditionAsync(string name, ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter, CancellationToken token)
{
throw new NotImplementedException();
}
public bool IsActionAsync(string name, string schemeCode)
{
return _asyncActions.ContainsKey(name);
}
public bool IsConditionAsync(string name, string schemeCode)
{
throw new NotImplementedException();
return false; //we have no async conditions now
}
public List<string> GetActions(string schemeCode, NamesSearchType namesSearchType)
{
return _asyncActions.Keys.ToList();
}
public List<string> GetConditions(string schemeCode, NamesSearchType namesSearchType)
{
return new List<string>();
return _syncConditions.Keys.ToList();
}
//it is internal just to have possibility to use nameof()
internal async Task SendMessageToProcessConsoleAsync(ProcessInstance processInstance, WorkflowRuntime runtime,
string actionParameter, CancellationToken token)
{
await _processConsoleHub.Clients.All.SendAsync("ReceiveMessage", new
{
processId = processInstance.ProcessId,
message = actionParameter
}, cancellationToken: token);
}
private bool IsHighPriority(ProcessInstance processInstance, WorkflowRuntime runtime, string actionParameter)
{
dynamic report = processInstance.GetParameter<DynamicParameter>("Report");
return (report.CompanySize == "Big" && report.Position == "Management")
|| (report.CompanySize == "Small" && report.Position == "Development");
}
private bool IsMediumPriority(ProcessInstance processInstance, WorkflowRuntime runtime, string actionParameter)
{
dynamic report = processInstance.GetParameter<DynamicParameter>("Report");
return report.Position != "Other" && !IsHighPriority(processInstance, runtime, actionParameter);
}
}
Starting the application​
Now, the application can be run by executing the following commands:
cd docker-files
docker compose up --build --force-recreate
Then, open the application URL http://localhost:3000/ in the browser.
Upload the process scheme​
The ConditionalBranches.xml
scheme provides a process example where different conditions will determinate the priorities for processing
the received inquiries.
Download the new ConditionalBranches.xml
here.
Click on tab Designer
to upload the process scheme as indicated below:
Then, save the scheme.
Running the process​
In this section, we are going to describe how the process can be run.
Process triggering​
Initially, we have the Parameters that represent the results to certain survey. These simple three parameters are: technology stack, amount of developers and job title.
Moreover, there are four methods for organizing conditional transitions:
- First, the Decision activity. It presents a quite simple condition where the technology stack is checked. If it matches
with our stack, then the process goes to next stage. Otherwise, the processing is terminated. The selected condition type is
Expression
which can be used by process parameters. This expression just takes the referred value: 'net' and validates if it is present.
When method ToLower
is called, the indicated @parameterName
must be written in parentheses to avoid confusion with the syntax
of partial parameters.
Detailed information regarding Expressions
can be read in this documentation section.
-
Second, the Decision table activity. It enables to include several conditions in an activity and editing them in tabular form. Here, the size of the company is defined according to the following statements:
@How_many_developers_do_you_have_working
> 0 AND@How_many_developers_do_you_have_working
<= 20.@How_many_developers_do_you_have_working
> 20 AND@How_many_developers_do_you_have_working
<= 100.@How_many_developers_do_you_have_working
> 100.
These Expressions allow to classify the company as small, medium or big considering the amount of developers that work for it. Depending on the expression that is fulfilled, the process moves on.
- Once the company is classified, then the basic plugin activity
SetParameter
is used for Small Company, Medium Company and Big Company. It sets the activity Name, State, Parameter name and its Value. The Parameter name creates an object calledReport
where the fieldCompanySize
will contain the values: "Small", "Medium" or "Big".
- Third, the Transitions that call
IsManagement
andIsDevelopment
CodeActions. These actions check the value of the parameter of the process that contains information about the job description of the person who completed the survey.
- These CodeActions are set in the transitions simply. Depending on the value that is valid, the transition is reached out, so there is not a specific order for calculating conditions.
In other cases, the type of condition is set as Otherwise
.
- Then, the
SetParameter
activities: Management, Development and Other are included. Here, the Parameter name creates the objectReport
with a fieldPosition
which will contain the values: "Management", "Development" or "Other".
- Fourth, the Transitions where
IsHighPriority
andIsMediumPriority
methods are invoked. These methods define the conditions for calculating the priorities High, Medium or Low. In other cases, the condition will be setOtherwise
. Further, a different criteria will be handled by Low Priority.
These Conditions are set in the ActionProvider
as it was explained in this section.
Process execution​
Now, lets execute the process. Just click on button Create process
to create a new process.
In this step you should simply indicate the technology stack, the amount of developers and the job title in the modal window 'Initial process parameters'. After that, click on blue button 'Create Process'.
Then, the inquiry will be automatically issued by considering the input data that have been provided.
In addition, click on Designer toolbar and check Process info
where the Report
object will be saved and the issued data will be
available.
Conclusion​
That's it! We have demonstrated in this tutorial four methods for organizing conditional transitions, and also we described how Conditions can be implemented in our WFE sample application.
Detailed information regarding Conditions
in Workflow Engine can be read here.