diff --git a/ConductorSharp.sln b/ConductorSharp.sln index 94732eff..6bd45c55 100644 --- a/ConductorSharp.sln +++ b/ConductorSharp.sln @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConductorSharp.Patterns", "src\ConductorSharp.Patterns\ConductorSharp.Patterns.csproj", "{9A55F1CF-2553-4F44-A8BC-0063A402582F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,6 +66,10 @@ Global {7FC97985-07CE-4643-8415-C48BA2117D88}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FC97985-07CE-4643-8415-C48BA2117D88}.Release|Any CPU.ActiveCfg = Release|Any CPU {7FC97985-07CE-4643-8415-C48BA2117D88}.Release|Any CPU.Build.0 = Release|Any CPU + {9A55F1CF-2553-4F44-A8BC-0063A402582F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A55F1CF-2553-4F44-A8BC-0063A402582F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A55F1CF-2553-4F44-A8BC-0063A402582F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A55F1CF-2553-4F44-A8BC-0063A402582F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/examples/ConductorSharp.Definitions/ConductorModule.cs b/examples/ConductorSharp.Definitions/ConductorModule.cs index 213e32c2..d5900942 100644 --- a/examples/ConductorSharp.Definitions/ConductorModule.cs +++ b/examples/ConductorSharp.Definitions/ConductorModule.cs @@ -11,6 +11,7 @@ protected override void Load(ContainerBuilder builder) base.Load(builder); builder.RegisterWorkflow(); + builder.RegisterWorkflow(); } } } diff --git a/examples/ConductorSharp.Definitions/ConductorSharp.Definitions.csproj b/examples/ConductorSharp.Definitions/ConductorSharp.Definitions.csproj index 6b12bd4d..a99093b7 100644 --- a/examples/ConductorSharp.Definitions/ConductorSharp.Definitions.csproj +++ b/examples/ConductorSharp.Definitions/ConductorSharp.Definitions.csproj @@ -35,6 +35,7 @@ + diff --git a/examples/ConductorSharp.Definitions/Program.cs b/examples/ConductorSharp.Definitions/Program.cs index 02c88e92..538f2fe2 100644 --- a/examples/ConductorSharp.Definitions/Program.cs +++ b/examples/ConductorSharp.Definitions/Program.cs @@ -3,6 +3,7 @@ using ConductorSharp.Definitions; using ConductorSharp.Engine.Extensions; using ConductorSharp.Engine.Health; +using ConductorSharp.Patterns.Extensions; using MediatR.Extensions.Autofac.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -40,6 +41,7 @@ longPollInterval: configuration.GetValue("Conductor:LongPollInterval"), domain: configuration.GetValue("Conductor:WorkerDomain") ) + .AddConductorSharpPatterns() .SetHealthCheckService() .AddPipelines(pipelines => { diff --git a/examples/ConductorSharp.Definitions/Workflows/HandleNotificationFailure.cs b/examples/ConductorSharp.Definitions/Workflows/HandleNotificationFailure.cs new file mode 100644 index 00000000..6cc89c6c --- /dev/null +++ b/examples/ConductorSharp.Definitions/Workflows/HandleNotificationFailure.cs @@ -0,0 +1,40 @@ +using ConductorSharp.Client.Model.Common; +using ConductorSharp.Engine.Builders; +using ConductorSharp.Engine.Util; +using ConductorSharp.Patterns.Tasks; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ConductorSharp.Definitions.Workflows +{ + public class HandleNotificationFailureInput : WorkflowInput + { + [JsonProperty("workflowId")] + public string? WorkflowId { get; set; } + } + + public class HandleNotificationFailureOutput : WorkflowOutput { } + + [OriginalName("NOTIFICATION_handle_failure")] + public class HandleNotificationFailure : Workflow + { + public ReadWorkflowTasks? ReadExecutedTasks { get; set; } + + public override WorkflowDefinition GetDefinition() + { + var builder = new WorkflowDefinitionBuilder(); + + builder.AddTask(a => a.ReadExecutedTasks, b => new() { TaskNames = "dynamic_handler", WorkflowId = b.WorkflowInput.WorkflowId }); + + return builder.Build(options => + { + options.Version = 1; + options.OwnerEmail = "example@example.local"; + }); + } + } +} diff --git a/examples/ConductorSharp.Definitions/Workflows/SendCustomerNotification.cs b/examples/ConductorSharp.Definitions/Workflows/SendCustomerNotification.cs index 8350fdd9..74090ed2 100644 --- a/examples/ConductorSharp.Definitions/Workflows/SendCustomerNotification.cs +++ b/examples/ConductorSharp.Definitions/Workflows/SendCustomerNotification.cs @@ -3,6 +3,7 @@ using ConductorSharp.Engine.Builders; using ConductorSharp.Engine.Model; using ConductorSharp.Engine.Util; +using ConductorSharp.Patterns.Tasks; using MediatR; namespace ConductorSharp.Definitions.Workflows @@ -28,6 +29,7 @@ public class SendCustomerNotification : Workflow? DynamicHandler { get; set; } public SendCustomerNotification? SendNotif { get; set; } + public WaitSeconds? WaitSeconds { get; set; } public override WorkflowDefinition GetDefinition() { @@ -43,10 +45,13 @@ public override WorkflowDefinition GetDefinition() } ); + builder.AddTask(a => a.WaitSeconds, b => new() { Seconds = 10 }); + builder.AddTask(a => a.PrepareEmail, b => new() { Address = b.DynamicHandler!.Output.Address, Name = b.DynamicHandler!.Output.Name }); return builder.Build(options => { + options.FailureWorkflow = typeof(HandleNotificationFailure); options.Version = 1; options.OwnerEmail = "example@example.local"; }); diff --git a/src/ConductorSharp.Engine/Builders/DecisionTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/DecisionTaskBuilder.cs index 977efb22..14b17db7 100644 --- a/src/ConductorSharp.Engine/Builders/DecisionTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/DecisionTaskBuilder.cs @@ -10,6 +10,28 @@ namespace ConductorSharp.Engine.Builders { + public static class DecisionTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression> taskSelector, + Expression> expression, + params (string, Action>)[] caseActions + ) where TWorkflow : ITypedWorkflow + { + var taskBbuilder = new DecisionTaskBuilder(taskSelector.Body, expression.Body); + + foreach (var funcase in caseActions) + { + taskBbuilder.AddCase(funcase.Item1); + funcase.Item2.Invoke(taskBbuilder); + } + + builder.Context.TaskBuilders.Add(taskBbuilder); + return taskBbuilder; + } + } + public class DecisionTaskBuilder : BaseTaskBuilder where TWorkflow : ITypedWorkflow { private Dictionary> _caseDictionary = new(); diff --git a/src/ConductorSharp.Engine/Builders/DynamicForkJoinTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/DynamicForkJoinTaskBuilder.cs index 1176905d..12f142a9 100644 --- a/src/ConductorSharp.Engine/Builders/DynamicForkJoinTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/DynamicForkJoinTaskBuilder.cs @@ -1,10 +1,26 @@ using ConductorSharp.Client.Model.Common; +using ConductorSharp.Engine.Interface; using ConductorSharp.Engine.Model; using Newtonsoft.Json.Linq; +using System; using System.Linq.Expressions; namespace ConductorSharp.Engine.Builders { + public static class DynamicForkJoinTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression> refference, + Expression> input + ) where TWorkflow : ITypedWorkflow + { + var taskBuilder = new DynamicForkJoinTaskBuilder(refference.Body, input.Body); + builder.Context.TaskBuilders.Add(taskBuilder); + return taskBuilder; + } + } + public class DynamicForkJoinTaskBuilder : BaseTaskBuilder { public DynamicForkJoinTaskBuilder(Expression taskExpression, Expression inputExpression) : base(taskExpression, inputExpression) { } diff --git a/src/ConductorSharp.Engine/Builders/DynamicTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/DynamicTaskBuilder.cs index e1bc67cf..ab2d015a 100644 --- a/src/ConductorSharp.Engine/Builders/DynamicTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/DynamicTaskBuilder.cs @@ -1,5 +1,7 @@ using ConductorSharp.Client.Model.Common; +using ConductorSharp.Engine.Interface; using ConductorSharp.Engine.Model; +using MediatR; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -9,6 +11,20 @@ namespace ConductorSharp.Engine.Builders { + public static class DynamicTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression>> reference, + Expression>> input + ) where TWorkflow : ITypedWorkflow + { + var taskBuilder = new DynamicTaskBuilder(reference.Body, input.Body); + builder.Context.TaskBuilders.Add(taskBuilder); + return taskBuilder; + } + } + public class DynamicTaskBuilder : BaseTaskBuilder, O> { private const string TaskType = "DYNAMIC"; diff --git a/src/ConductorSharp.Engine/Builders/JsonJqTransformTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/JsonJqTransformTaskBuilder.cs index 3188ee33..c9b9d0c6 100644 --- a/src/ConductorSharp.Engine/Builders/JsonJqTransformTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/JsonJqTransformTaskBuilder.cs @@ -1,10 +1,28 @@ using ConductorSharp.Client.Model.Common; +using ConductorSharp.Engine.Interface; +using ConductorSharp.Engine.Model; using MediatR; using System; using System.Linq.Expressions; namespace ConductorSharp.Engine.Builders { + public static class JsonJqTransformTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression>> refference, + Expression> input + ) + where TWorkflow : ITypedWorkflow + where F : IRequest + { + var taskBuilder = new JsonJqTransformTaskBuilder(refference.Body, input.Body); + builder.Context.TaskBuilders.Add(taskBuilder); + return taskBuilder; + } + } + public class JsonJqTransformTaskBuilder : BaseTaskBuilder where A : IRequest { public JsonJqTransformTaskBuilder(Expression taskExpression, Expression inputExpression) : base(taskExpression, inputExpression) diff --git a/src/ConductorSharp.Engine/Builders/LambdaTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/LambdaTaskBuilder.cs index 588df4fc..2aeb9dde 100644 --- a/src/ConductorSharp.Engine/Builders/LambdaTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/LambdaTaskBuilder.cs @@ -1,10 +1,30 @@ using ConductorSharp.Client.Model.Common; +using ConductorSharp.Engine.Interface; +using ConductorSharp.Engine.Model; using MediatR; using Newtonsoft.Json.Linq; +using System; using System.Linq.Expressions; namespace ConductorSharp.Engine.Builders { + public static class LambdaTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression>> referrence, + Expression> input, + string script + ) + where TWorkflow : ITypedWorkflow + where F : IRequest + { + var taskBuilder = new LambdaTaskBuilder(script, referrence.Body, input.Body); + builder.Context.TaskBuilders.Add(taskBuilder); + return taskBuilder; + } + } + public class LambdaTaskBuilder : BaseTaskBuilder where A : IRequest { private readonly string _script; diff --git a/src/ConductorSharp.Engine/Builders/PassThroughTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/PassThroughTaskBuilder.cs index 8075aa21..2ee73362 100644 --- a/src/ConductorSharp.Engine/Builders/PassThroughTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/PassThroughTaskBuilder.cs @@ -6,6 +6,12 @@ namespace ConductorSharp.Engine.Builders { + public static class PassThroughTaskExtensions + { + public static void AddTasks(this WorkflowDefinitionBuilder builder, params WorkflowDefinition.Task[] taskDefinitions) + where TWorkflow : ITypedWorkflow => builder.Context.TaskBuilders.Add(new PassThroughTaskBuilder(taskDefinitions)); + } + public class PassThroughTaskBuilder : ITaskBuilder { private readonly WorkflowDefinition.Task[] _tasks; diff --git a/src/ConductorSharp.Engine/Builders/SimpleTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/SimpleTaskBuilder.cs index 19e5f0c9..a3547944 100644 --- a/src/ConductorSharp.Engine/Builders/SimpleTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/SimpleTaskBuilder.cs @@ -1,11 +1,30 @@ using ConductorSharp.Client.Model.Common; +using ConductorSharp.Engine.Interface; using ConductorSharp.Engine.Model; using MediatR; using Newtonsoft.Json.Linq; +using System; using System.Linq.Expressions; namespace ConductorSharp.Engine.Builders { + public static class SimpleTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression>> refference, + Expression> input, + AdditionalTaskParameters additionalParameters = null + ) + where TWorkflow : ITypedWorkflow + where F : IRequest + { + var taskBuilder = new SimpleTaskBuilder(refference.Body, input.Body, additionalParameters); + builder.Context.TaskBuilders.Add(taskBuilder); + return taskBuilder; + } + } + public class SimpleTaskBuilder : BaseTaskBuilder where A : IRequest { public SimpleTaskBuilder(Expression taskExpression, Expression inputExpression, AdditionalTaskParameters additionalParameters) diff --git a/src/ConductorSharp.Engine/Builders/SubWorkflowTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/SubWorkflowTaskBuilder.cs index 43d53afc..3049f0f4 100644 --- a/src/ConductorSharp.Engine/Builders/SubWorkflowTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/SubWorkflowTaskBuilder.cs @@ -1,12 +1,31 @@ using ConductorSharp.Client.Model.Common; +using ConductorSharp.Engine.Interface; +using ConductorSharp.Engine.Model; using ConductorSharp.Engine.Util; using MediatR; using Newtonsoft.Json.Linq; +using System; using System.Linq.Expressions; using System.Reflection; namespace ConductorSharp.Engine.Builders { + public static class SubWorkflowTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression>> referrence, + Expression> input + ) + where TWorkflow : ITypedWorkflow + where F : IRequest + { + var taskBuilder = new SubWorkflowTaskBuilder(referrence.Body, input.Body); + builder.Context.TaskBuilders.Add(taskBuilder); + return taskBuilder; + } + } + public class SubWorkflowTaskBuilder : BaseTaskBuilder where TInput : IRequest { private readonly int _version; diff --git a/src/ConductorSharp.Engine/Builders/SwitchTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/SwitchTaskBuilder.cs index 363ff098..2b039538 100644 --- a/src/ConductorSharp.Engine/Builders/SwitchTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/SwitchTaskBuilder.cs @@ -10,6 +10,28 @@ namespace ConductorSharp.Engine.Builders { + public static class SwitchTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression> taskSelector, + Expression> expression, + params (string, Action>)[] caseActions + ) where TWorkflow : ITypedWorkflow + { + var taskBuilder = new SwitchTaskBuilder(taskSelector.Body, expression.Body); + + foreach (var funcase in caseActions) + { + taskBuilder.AddCase(funcase.Item1); + funcase.Item2.Invoke(taskBuilder); + } + + builder.Context.TaskBuilders.Add(taskBuilder); + return taskBuilder; + } + } + public class SwitchTaskBuilder : BaseTaskBuilder where TWorkflow : ITypedWorkflow { private Dictionary> _caseDictionary = new(); diff --git a/src/ConductorSharp.Engine/Builders/TerminateTaskBuilder.cs b/src/ConductorSharp.Engine/Builders/TerminateTaskBuilder.cs index 45c8693f..f3ebd0d1 100644 --- a/src/ConductorSharp.Engine/Builders/TerminateTaskBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/TerminateTaskBuilder.cs @@ -1,4 +1,5 @@ using ConductorSharp.Client.Model.Common; +using ConductorSharp.Engine.Interface; using ConductorSharp.Engine.Model; using Newtonsoft.Json.Linq; using System; @@ -8,6 +9,20 @@ namespace ConductorSharp.Engine.Builders { + public static class TerminateTaskExtensions + { + public static ITaskOptionsBuilder AddTask( + this WorkflowDefinitionBuilder builder, + Expression> reference, + Expression> input + ) where TWorkflow : ITypedWorkflow + { + var taskBuilder = new TerminateTaskBuilder(reference.Body, input.Body); + builder.Context.TaskBuilders.Add(taskBuilder); + return taskBuilder; + } + } + internal class TerminateTaskBuilder : BaseTaskBuilder { public TerminateTaskBuilder(Expression taskExpression, Expression memberExpression) : base(taskExpression, memberExpression) { } diff --git a/src/ConductorSharp.Engine/Builders/WorkflowDefinitionBuilder.cs b/src/ConductorSharp.Engine/Builders/WorkflowDefinitionBuilder.cs index f1d1f6d1..c8d3897c 100644 --- a/src/ConductorSharp.Engine/Builders/WorkflowDefinitionBuilder.cs +++ b/src/ConductorSharp.Engine/Builders/WorkflowDefinitionBuilder.cs @@ -14,19 +14,23 @@ namespace ConductorSharp.Engine.Builders { + public class DefinitionContext + { + public WorkflowOptions WorkflowOptions { get; } = new(); + + public List TaskBuilders { get; } = new(); + } + public class WorkflowDefinitionBuilder where TWorkflow : ITypedWorkflow { private readonly Type _workflowType; private string _name; private JObject _inputs = new(); - private WorkflowOptions _workflowOptions; - - private List _taskBuilders = new(); + public DefinitionContext Context { get; } = new(); public WorkflowDefinitionBuilder() { _workflowType = typeof(TWorkflow); - _workflowOptions = new WorkflowOptions(); XmlDocumentationReader.LoadXmlDocumentation(_workflowType.Assembly); _name = NamingUtil.DetermineRegistrationName(_workflowType); @@ -36,19 +40,19 @@ public WorkflowDefinitionBuilder() var ownerEmail = _workflowType.GetDocSection("ownerEmail"); var labels = _workflowType.GetDocSection("labels"); - _workflowOptions.Version = 1; + Context.WorkflowOptions.Version = 1; if (!string.IsNullOrEmpty(summary)) - _workflowOptions.Description = summary; + Context.WorkflowOptions.Description = summary; if (!string.IsNullOrEmpty(ownerApp)) - _workflowOptions.OwnerApp = ownerApp; + Context.WorkflowOptions.OwnerApp = ownerApp; if (!string.IsNullOrEmpty(ownerEmail)) - _workflowOptions.OwnerEmail = ownerEmail; + Context.WorkflowOptions.OwnerEmail = ownerEmail; if (!string.IsNullOrEmpty(labels)) - _workflowOptions.Labels = labels.Split(",").Select(a => a.Trim()).ToArray(); + Context.WorkflowOptions.Labels = labels.Split(",").Select(a => a.Trim()).ToArray(); var input = _workflowType.BaseType.GenericTypeArguments[0]; var props = input.GetProperties(); @@ -72,104 +76,25 @@ public WorkflowDefinitionBuilder() public WorkflowDefinition Build(Action adjustOptions) { - adjustOptions.Invoke(_workflowOptions); + adjustOptions.Invoke(Context.WorkflowOptions); return new WorkflowDefinition { Name = _name, - Tasks = _taskBuilders.SelectMany(a => a.Build()).ToList(), + Tasks = Context.TaskBuilders.SelectMany(a => a.Build()).ToList(), FailureWorkflow = - _workflowOptions.FailureWorkflow != null ? NamingUtil.DetermineRegistrationName(_workflowOptions.FailureWorkflow) : null, + Context.WorkflowOptions.FailureWorkflow != null + ? NamingUtil.DetermineRegistrationName(Context.WorkflowOptions.FailureWorkflow) + : null, Description = new JObject() { - new JProperty("description", _workflowOptions.Description), - new JProperty("labels", _workflowOptions.Labels) + new JProperty("description", Context.WorkflowOptions.Description), + new JProperty("labels", Context.WorkflowOptions.Labels) }.ToString(Formatting.None), InputParameters = _inputs, - OwnerApp = _workflowOptions.OwnerApp, - OwnerEmail = _workflowOptions.OwnerEmail, + OwnerApp = Context.WorkflowOptions.OwnerApp, + OwnerEmail = Context.WorkflowOptions.OwnerEmail, }; } - - public ITaskOptionsBuilder AddTask( - Expression>> referrence, - Expression> input, - string script - ) where F : IRequest => AddAndReturnBuilder(new LambdaTaskBuilder(script, referrence.Body, input.Body)); - - public ITaskOptionsBuilder AddTask( - Expression>> referrence, - Expression> input - ) where F : IRequest => AddAndReturnBuilder(new SubWorkflowTaskBuilder(referrence.Body, input.Body)); - - public ITaskOptionsBuilder AddTask( - Expression> refference, - Expression> input - ) => AddAndReturnBuilder(new DynamicForkJoinTaskBuilder(refference.Body, input.Body)); - - public void AddTasks(params WorkflowDefinition.Task[] taskDefinitions) => _taskBuilders.Add(new PassThroughTaskBuilder(taskDefinitions)); - - public ITaskOptionsBuilder AddTask( - Expression>> refference, - Expression> input, - AdditionalTaskParameters additionalParameters = null - ) where F : IRequest => AddAndReturnBuilder(new SimpleTaskBuilder(refference.Body, input.Body, additionalParameters)); - - public ITaskOptionsBuilder AddTask( - Expression> taskSelector, - Expression> expression, - params (string, Action>)[] caseActions - ) - { - var builder = new DecisionTaskBuilder(taskSelector.Body, expression.Body); - - foreach (var funcase in caseActions) - { - builder.AddCase(funcase.Item1); - funcase.Item2.Invoke(builder); - } - - _taskBuilders.Add(builder); - return builder; - } - - public ITaskOptionsBuilder AddTask( - Expression> taskSelector, - Expression> expression, - params (string, Action>)[] caseActions - ) - { - var builder = new SwitchTaskBuilder(taskSelector.Body, expression.Body); - - foreach (var funcase in caseActions) - { - builder.AddCase(funcase.Item1); - funcase.Item2.Invoke(builder); - } - - _taskBuilders.Add(builder); - return builder; - } - - public ITaskOptionsBuilder AddTask( - Expression>> refference, - Expression> input - ) where F : IRequest => AddAndReturnBuilder(new JsonJqTransformTaskBuilder(refference.Body, input.Body)); - - public ITaskOptionsBuilder AddTask( - Expression>> reference, - Expression>> input - ) => AddAndReturnBuilder(new DynamicTaskBuilder(reference.Body, input.Body)); - - public ITaskOptionsBuilder AddTask( - Expression> reference, - Expression> input - ) => AddAndReturnBuilder(new TerminateTaskBuilder(reference.Body, input.Body)); - - private ITaskOptionsBuilder AddAndReturnBuilder(T builder) where T : ITaskOptionsBuilder, ITaskBuilder - { - _taskBuilders.Add(builder); - return builder; - } } } diff --git a/src/ConductorSharp.Engine/Extensions/ConductorSharpBuilder.cs b/src/ConductorSharp.Engine/Extensions/ConductorSharpBuilder.cs index 2bca4b78..e58ef7ab 100644 --- a/src/ConductorSharp.Engine/Extensions/ConductorSharpBuilder.cs +++ b/src/ConductorSharp.Engine/Extensions/ConductorSharpBuilder.cs @@ -15,9 +15,9 @@ namespace ConductorSharp.Engine.Extensions { public class ConductorSharpBuilder : IConductorSharpBuilder, IExecutionManagerBuilder, IPipelineBuilder { - private readonly ContainerBuilder _builder; + public ContainerBuilder Builder { get; set; } - public ConductorSharpBuilder(ContainerBuilder builder) => _builder = builder; + public ConductorSharpBuilder(ContainerBuilder builder) => Builder = builder; public IExecutionManagerBuilder AddExecutionManager(int maxConcurrentWorkers, int sleepInterval, int longPollInterval, string domain = null) { @@ -29,23 +29,23 @@ public IExecutionManagerBuilder AddExecutionManager(int maxConcurrentWorkers, in SleepInterval = sleepInterval }; - _builder.RegisterInstance(workerConfig).SingleInstance(); + Builder.RegisterInstance(workerConfig).SingleInstance(); - _builder.RegisterType().As(); + Builder.RegisterType().As(); - _builder.RegisterType().As(); + Builder.RegisterType().As(); - _builder.RegisterType(); + Builder.RegisterType(); - _builder.RegisterType().SingleInstance(); + Builder.RegisterType().SingleInstance(); - _builder.RegisterType().InstancePerLifetimeScope(); + Builder.RegisterType().InstancePerLifetimeScope(); - _builder.RegisterType().As().SingleInstance(); + Builder.RegisterType().As().SingleInstance(); - _builder.RegisterType().As(); + Builder.RegisterType().As(); - _builder.RegisterType().As(); + Builder.RegisterType().As(); return this; } @@ -57,15 +57,15 @@ public IExecutionManagerBuilder AddPipelines(Action behaviorBu } public void AddRequestResponseLogging() => - _builder.RegisterGeneric(typeof(RequestResponseLoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); + Builder.RegisterGeneric(typeof(RequestResponseLoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); - public void AddValidation() => _builder.RegisterGeneric(typeof(ValidationBehavior<,>)).As(typeof(IPipelineBehavior<,>)); + public void AddValidation() => Builder.RegisterGeneric(typeof(ValidationBehavior<,>)).As(typeof(IPipelineBehavior<,>)); - public void AddContextLogging() => _builder.RegisterGeneric(typeof(ContextLoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); + public void AddContextLogging() => Builder.RegisterGeneric(typeof(ContextLoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>)); public IExecutionManagerBuilder SetHealthCheckService() where T : IConductorSharpHealthService { - _builder.RegisterType().As().SingleInstance(); + Builder.RegisterType().As().SingleInstance(); return this; } } diff --git a/src/ConductorSharp.Engine/Extensions/IExecutionManagerBuilder.cs b/src/ConductorSharp.Engine/Extensions/IExecutionManagerBuilder.cs index f758aff4..9d7fc588 100644 --- a/src/ConductorSharp.Engine/Extensions/IExecutionManagerBuilder.cs +++ b/src/ConductorSharp.Engine/Extensions/IExecutionManagerBuilder.cs @@ -1,4 +1,5 @@ -using ConductorSharp.Engine.Health; +using Autofac; +using ConductorSharp.Engine.Health; using System; using System.Collections.Generic; using System.Text; @@ -7,6 +8,7 @@ namespace ConductorSharp.Engine.Extensions { public interface IExecutionManagerBuilder { + ContainerBuilder Builder { get; set; } IExecutionManagerBuilder AddPipelines(Action pipelines); IExecutionManagerBuilder SetHealthCheckService() where T : IConductorSharpHealthService; } diff --git a/src/ConductorSharp.Patterns/ConductorSharp.Patterns.csproj b/src/ConductorSharp.Patterns/ConductorSharp.Patterns.csproj new file mode 100644 index 00000000..7cfa0d0b --- /dev/null +++ b/src/ConductorSharp.Patterns/ConductorSharp.Patterns.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.1 + disable + + + + + + + + + + + + + diff --git a/src/ConductorSharp.Patterns/Constants.cs b/src/ConductorSharp.Patterns/Constants.cs new file mode 100644 index 00000000..3924daf2 --- /dev/null +++ b/src/ConductorSharp.Patterns/Constants.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ConductorSharp.Patterns +{ + public static class Constants + { + public const string TaskNamePrefix = "CSH_PATTERNS"; + } +} diff --git a/src/ConductorSharp.Patterns/Extensions/ContainerBuilderExtensions.cs b/src/ConductorSharp.Patterns/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 00000000..0900dd02 --- /dev/null +++ b/src/ConductorSharp.Patterns/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,21 @@ +using ConductorSharp.Engine.Extensions; +using ConductorSharp.Patterns.Tasks; +using System; +using System.Collections.Generic; +using System.Text; +using MediatR.Extensions.Autofac.DependencyInjection; + +namespace ConductorSharp.Patterns.Extensions +{ + public static class ContainerBuilderExtensions + { + public static IExecutionManagerBuilder AddConductorSharpPatterns(this IExecutionManagerBuilder executionManagerBuilder) + { + executionManagerBuilder.Builder.RegisterWorkerTask(); + executionManagerBuilder.Builder.RegisterWorkerTask(); + executionManagerBuilder.Builder.RegisterMediatR(typeof(WaitSeconds).Assembly); + + return executionManagerBuilder; + } + } +} diff --git a/src/ConductorSharp.Patterns/Tasks/ReadWorkflowTasks.cs b/src/ConductorSharp.Patterns/Tasks/ReadWorkflowTasks.cs new file mode 100644 index 00000000..d3613005 --- /dev/null +++ b/src/ConductorSharp.Patterns/Tasks/ReadWorkflowTasks.cs @@ -0,0 +1,102 @@ +using ConductorSharp.Client.Service; +using ConductorSharp.Engine; +using ConductorSharp.Engine.Util; +using MediatR; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ConductorSharp.Patterns.Tasks +{ + #region models + public class ReadWorkflowTasksRequest : IRequest + { + /// + /// Comma separated list of task reference names to be read from specified workflow + /// + [Required] + public string TaskNames { get; set; } + + /// + /// Id of the workflow to read tasks from + /// + [Required] + public string WorkflowId { get; set; } + } + + public class ReadWorkflowTasksResponse + { + public Dictionary Tasks { get; set; } + public WorkflowDetails Workflow { get; set; } + } + + public class WorkflowDetails + { + public JObject InputData { get; set; } + } + + public class TaskExecutionDetails + { + [JsonProperty("inputData")] + public JObject InputData { get; set; } = new JObject(); + + [JsonProperty("outputData")] + public JObject OutputData { get; set; } = new JObject(); + + [JsonProperty("referenceTaskName")] + public string ReferenceTaskName { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } = "NOT_FOUND"; + } + #endregion + /// + /// Uses the Conductor API to read the input/output and status of the specified tasks for the specified workflow. + /// + [OriginalName(Constants.TaskNamePrefix + "_read_tasks")] + public class ReadWorkflowTasks : TaskRequestHandler + { + private readonly ILogger _logger; + private readonly IWorkflowService _workflowService; + + public ReadWorkflowTasks(ILogger logger, IWorkflowService workflowService) + { + _logger = logger; + _workflowService = workflowService; + } + + public async override Task Handle(ReadWorkflowTasksRequest input, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(input.TaskNames)) + throw new Exception("No task names provided. Comma separated list of reference names expected"); + + var tasknames = input.TaskNames.Split(",").Where(a => !string.IsNullOrEmpty(a)).ToList(); + + var starterWorkflow = await _workflowService.GetWorkflowStatus(input.WorkflowId); + + if (starterWorkflow == null) + throw new Exception($"Could not find starter workflow by id {input.WorkflowId}"); + + var taskData = starterWorkflow.SelectToken("tasks").ToObject>(); + + var output = new ReadWorkflowTasksResponse + { + Workflow = new WorkflowDetails { InputData = starterWorkflow.SelectToken("input") as JObject }, + Tasks = new Dictionary() + }; + + foreach (var task in tasknames) + { + output.Tasks.Add(task, taskData.FirstOrDefault(a => a.ReferenceTaskName == task) ?? new TaskExecutionDetails()); + } + + return output; + } + } +} diff --git a/src/ConductorSharp.Patterns/Tasks/WaitSeconds.cs b/src/ConductorSharp.Patterns/Tasks/WaitSeconds.cs new file mode 100644 index 00000000..e4b8cb20 --- /dev/null +++ b/src/ConductorSharp.Patterns/Tasks/WaitSeconds.cs @@ -0,0 +1,41 @@ +using ConductorSharp.Engine; +using ConductorSharp.Engine.Model; +using ConductorSharp.Engine.Util; +using MediatR; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ConductorSharp.Patterns.Tasks +{ + public class WaitSecondsRequest : IRequest + { + /// + /// Time to wait in seconds + /// + [Required] + [Range(1, int.MaxValue)] + public int Seconds { get; set; } + } + + /// + /// Executes `await Task.Delay(input.Seconds * 1000)` to wait for a given amount of seconds + /// + [OriginalName(Constants.TaskNamePrefix + "_wait_seconds")] + public class WaitSeconds : TaskRequestHandler + { + private readonly ILogger _logger; + + public WaitSeconds(ILogger logger) => _logger = logger; + + public async override Task Handle(WaitSecondsRequest input, CancellationToken cancellationToken) + { + await Task.Delay(input.Seconds * 1000); + return new NoOutput(); + } + } +} diff --git a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj index 09c8ee6f..abadcacd 100644 --- a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj +++ b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj @@ -15,6 +15,7 @@ + @@ -31,6 +32,7 @@ + @@ -55,6 +57,7 @@ + diff --git a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs index a724a325..3c095244 100644 --- a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs +++ b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs @@ -114,5 +114,14 @@ public void BuilderReturnsCorrectDefinitionSubworkflowVersionAttribute() Assert.Equal(expectedDefinition, definition); } + + [Fact] + public void BuilderReturnsCorrectDefinitionPatternTasks() + { + var definition = SerializationUtil.SerializeObject(new PatternTasks().GetDefinition()); + var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Workflows/PatternTasks.json"); + + Assert.Equal(expectedDefinition, definition); + } } } diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/PatternTasks.cs b/test/ConductorSharp.Engine.Tests/Samples/Workflows/PatternTasks.cs new file mode 100644 index 00000000..828629ac --- /dev/null +++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/PatternTasks.cs @@ -0,0 +1,34 @@ +using ConductorSharp.Patterns.Tasks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ConductorSharp.Engine.Tests.Samples.Workflows +{ + public class PatternTasksInput : WorkflowInput + { + public int Seconds { get; set; } + public string WorkflowId { get; set; } + } + + public class PatternTasksOutput : WorkflowOutput { } + + public class PatternTasks : Workflow + { + public WaitSeconds WaitSeconds { get; set; } + public ReadWorkflowTasks ReadWorkflowTasks { get; set; } + + public override WorkflowDefinition GetDefinition() + { + var builder = new WorkflowDefinitionBuilder(); + + builder.AddTask(wf => wf.ReadWorkflowTasks, wf => new() { TaskNames = "task1,task2", WorkflowId = wf.WorkflowInput.WorkflowId }); + + builder.AddTask(wf => wf.WaitSeconds, wf => new() { Seconds = wf.WorkflowInput.Seconds }); + + return builder.Build(opts => opts.Version = 1); + } + } +} diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/PatternTasks.json b/test/ConductorSharp.Engine.Tests/Samples/Workflows/PatternTasks.json new file mode 100644 index 00000000..c8edf93f --- /dev/null +++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/PatternTasks.json @@ -0,0 +1,91 @@ +{ + "ownerApp": null, + "createTime": 0, + "updateTime": 0, + "createdBy": null, + "updatedBy": null, + "name": "pattern_tasks", + "description": "{\"description\":null,\"labels\":null}", + "version": 1, + "tasks": [ + { + "queryExpression": null, + "name": "CSH_PATTERNS_read_tasks", + "taskReferenceName": "read_workflow_tasks", + "description": "{\"description\":null}", + "inputParameters": { + "task_names": "task1,task2", + "workflow_id": "${workflow.input.workflow_id}" + }, + "type": "SIMPLE", + "dynamicTaskNameParam": null, + "caseValueParam": null, + "caseExpression": null, + "expression": null, + "evaluatorType": null, + "scriptExpression": null, + "decisionCases": null, + "dynamicForkJoinTasksParam": null, + "dynamicForkTasksParam": null, + "dynamicForkTasksInputParamName": null, + "defaultCase": null, + "forkTasks": null, + "startDelay": 0, + "subWorkflowParam": null, + "joinOn": null, + "sink": null, + "optional": false, + "taskDefinition": null, + "rateLimited": false, + "defaultExclusiveJoinTask": null, + "asyncComplete": false, + "loopCondition": null, + "loopOver": null + }, + { + "queryExpression": null, + "name": "CSH_PATTERNS_wait_seconds", + "taskReferenceName": "wait_seconds", + "description": "{\"description\":null}", + "inputParameters": { + "seconds": "${workflow.input.seconds}" + }, + "type": "SIMPLE", + "dynamicTaskNameParam": null, + "caseValueParam": null, + "caseExpression": null, + "expression": null, + "evaluatorType": null, + "scriptExpression": null, + "decisionCases": null, + "dynamicForkJoinTasksParam": null, + "dynamicForkTasksParam": null, + "dynamicForkTasksInputParamName": null, + "defaultCase": null, + "forkTasks": null, + "startDelay": 0, + "subWorkflowParam": null, + "joinOn": null, + "sink": null, + "optional": false, + "taskDefinition": null, + "rateLimited": false, + "defaultExclusiveJoinTask": null, + "asyncComplete": false, + "loopCondition": null, + "loopOver": null + } + ], + "inputParameters": [ + "{\"seconds\":{\"value\":\"\",\"description\":\" (optional)\"},\"workflow_id\":{\"value\":\"\",\"description\":\" (optional)\"}}" + ], + "outputParameters": null, + "failureWorkflow": null, + "schemaVersion": 2, + "restartable": true, + "workflowStatusListenerEnabled": true, + "ownerEmail": null, + "timeoutPolicy": null, + "timeoutSeconds": 0, + "variables": null +} \ No newline at end of file diff --git a/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs index a937117f..83c8a821 100644 --- a/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs +++ b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; -namespace ConductorSharp.Engine.Util +namespace ConductorSharp.Engine.Tests.Util { internal static class EmbeddedFileHelper {