Dynamic Workflows Low Code Style
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace dynamicworkflows.Workflows | |
{ | |
using Jint; | |
using Newtonsoft.Json; | |
using System.Collections.Generic; | |
using System.Net.Http; | |
using System.Threading.Tasks; | |
using Microsoft.Azure.WebJobs; | |
using Microsoft.Azure.WebJobs.Extensions.DurableTask; | |
using Microsoft.Azure.WebJobs.Extensions.Http; | |
using Microsoft.Extensions.Logging; | |
public class DynamicStep<T, U> | |
{ | |
public string Action { get; private set; } | |
public U param { get; private set; } | |
public string Fn { get; } | |
public DynamicStep(string action, U param) | |
{ | |
Action = action; | |
this.param = param; | |
Fn = string.Empty; | |
} | |
[JsonConstructor] | |
public DynamicStep(string action, U param, string fn) | |
{ | |
Action = action; | |
this.param = param; | |
Fn = fn; | |
} | |
} | |
public class DynamicResult<T> | |
{ | |
public T Result { get; set; } | |
} | |
public static class ActionName | |
{ | |
public const string Add = "Add"; | |
public const string Subtract = "Subtract"; | |
public const string Multiply = "Multiply"; | |
public const string Divide = "Divide"; | |
public const string Dynamic = "Dynamic"; | |
} | |
public static class FlowMaker | |
{ | |
[FunctionName("FlowMaker")] | |
public static async Task<double> Run([OrchestrationTrigger] IDurableOrchestrationContext context) | |
{ | |
var steps = new List<DynamicStep<int, int>> | |
{ | |
new DynamicStep<int, int>(ActionName.Add, 1), | |
new DynamicStep<int, int>(ActionName.Add, 2), | |
new DynamicStep<int, int>(ActionName.Add, 3), | |
new DynamicStep<int, int>(ActionName.Dynamic, 2, "(2 * r + 1)/p"), // <-- simulate loading for a datasource | |
}; | |
var ctx = new DynamicFlowContext | |
{ | |
Steps = steps | |
}; | |
var result = await context.CallSubOrchestratorAsync<DynamicResult<double>>("DynamicOrchestrator", ctx); | |
return result.Result; | |
} | |
[FunctionName("DynamicOrchestrator")] | |
public static async Task<DynamicResult<double>> RunInnerOrchestrator( | |
[OrchestrationTrigger] IDurableOrchestrationContext ctx) | |
{ | |
var input = ctx.GetInput<DynamicFlowContext>(); | |
double state = 0; | |
foreach (var step in input.Steps) | |
{ | |
state = await ctx.CallActivityAsync<double>(step.Action, new DynamicParam | |
{ | |
Accumulator = state, | |
Parameter = step.param, | |
Fn = step.Fn, | |
}); | |
} | |
return new DynamicResult<double> | |
{ | |
Result = state | |
}; | |
} | |
[FunctionName("Add")] | |
public static async Task<double> Add([ActivityTrigger] DynamicParam param, ILogger log) | |
{ | |
return param.Accumulator + param.Parameter; | |
} | |
[FunctionName("Subtract")] | |
public static async Task<double> Subtract([ActivityTrigger] DynamicParam param, ILogger log) | |
{ | |
return param.Accumulator - param.Parameter; | |
} | |
[FunctionName("Multiply")] | |
public static async Task<double> Multiply([ActivityTrigger] DynamicParam param, ILogger log) | |
{ | |
return param.Accumulator * param.Parameter; | |
} | |
[FunctionName("Divide")] | |
public static async Task<double> Divide([ActivityTrigger] DynamicParam param, ILogger log) | |
{ | |
return param.Accumulator / param.Parameter; | |
} | |
[FunctionName("Dynamic")] | |
public static async Task<double> DynamicCalculate([ActivityTrigger] DynamicParam param, ILogger log) | |
{ | |
var func = new Engine() | |
.Execute($"function dyn(r, p){{ return {param.Fn} }}").GetValue("dyn"); | |
var invoked = func.Invoke(param.Accumulator, param.Parameter); | |
double.TryParse(invoked.ToString(), out var result); | |
return result; | |
} | |
[FunctionName("FlowMaker_HttpStart")] | |
public static async Task<HttpResponseMessage> HttpStart( | |
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "start")] | |
HttpRequestMessage req, | |
[DurableClient] IDurableOrchestrationClient starter, | |
ILogger log) | |
{ | |
// Function input comes from the request content. | |
string instanceId = await starter.StartNewAsync("FlowMaker"); | |
log.LogInformation($"Started orchestration with ID = '{instanceId}'."); | |
return starter.CreateCheckStatusResponse(req, instanceId); | |
} | |
public class DynamicFlowContext | |
{ | |
public List<DynamicStep<int, int>> Steps { get; set; } | |
} | |
public class DynamicParam | |
{ | |
public double Accumulator { get; set; } | |
public int Parameter { get; set; } | |
public string Fn { get; set; } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment