Basic functioning workflow engine

This commit is contained in:
Sipke Schoorstra 2018-10-12 20:53:06 +02:00
parent d659e38c6f
commit 1635618b01
39 changed files with 1146 additions and 0 deletions

8
.editorconfig Normal file
View file

@ -0,0 +1,8 @@
[*]
charset=utf-8
end_of_line=crlf
trim_trailing_whitespace=false
insert_final_newline=false
indent_style=space
indent_size=4

39
.gitignore vendored Normal file
View file

@ -0,0 +1,39 @@
#Ignore thumbnails created by Windows
Thumbs.db
#Ignore files built by Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
.vs/
#Nuget packages folder
packages/
#Ignore git-related files
*.orig
#Rider
.idea

58
Flowsharp.sln Normal file
View file

@ -0,0 +1,58 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2036
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flowsharp.Abstractions", "src\Flowsharp.Abstractions\Flowsharp.Abstractions.csproj", "{300EE2D5-54C5-46F2-AD03-BB43589EA074}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flowsharp.Core", "src\Flowsharp.Core\Flowsharp.Core.csproj", "{3B33AE9C-0465-4DA3-8C02-65E5766A7ED4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flowsharp.Samples.Console", "src\Flowsharp.Samples.Console\Flowsharp.Samples.Console.csproj", "{48EDB976-3227-4DFD-BBB3-3BC9AEA19A88}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "items", "items", "{7165BB9E-F22C-40D2-B7B1-CA3EFA2529A0}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flowsharp.Fluid", "src\Flowsharp.Fluid\Flowsharp.Fluid.csproj", "{B20D6AF5-91B1-4455-8541-22C5B31288D0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{300EE2D5-54C5-46F2-AD03-BB43589EA074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{300EE2D5-54C5-46F2-AD03-BB43589EA074}.Debug|Any CPU.Build.0 = Debug|Any CPU
{300EE2D5-54C5-46F2-AD03-BB43589EA074}.Release|Any CPU.ActiveCfg = Release|Any CPU
{300EE2D5-54C5-46F2-AD03-BB43589EA074}.Release|Any CPU.Build.0 = Release|Any CPU
{3B33AE9C-0465-4DA3-8C02-65E5766A7ED4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B33AE9C-0465-4DA3-8C02-65E5766A7ED4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B33AE9C-0465-4DA3-8C02-65E5766A7ED4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B33AE9C-0465-4DA3-8C02-65E5766A7ED4}.Release|Any CPU.Build.0 = Release|Any CPU
{48EDB976-3227-4DFD-BBB3-3BC9AEA19A88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48EDB976-3227-4DFD-BBB3-3BC9AEA19A88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48EDB976-3227-4DFD-BBB3-3BC9AEA19A88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48EDB976-3227-4DFD-BBB3-3BC9AEA19A88}.Release|Any CPU.Build.0 = Release|Any CPU
{B20D6AF5-91B1-4455-8541-22C5B31288D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B20D6AF5-91B1-4455-8541-22C5B31288D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B20D6AF5-91B1-4455-8541-22C5B31288D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B20D6AF5-91B1-4455-8541-22C5B31288D0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{300EE2D5-54C5-46F2-AD03-BB43589EA074} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
{3B33AE9C-0465-4DA3-8C02-65E5766A7ED4} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
{48EDB976-3227-4DFD-BBB3-3BC9AEA19A88} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
{B20D6AF5-91B1-4455-8541-22C5B31288D0} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,135 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
using Microsoft.Extensions.Localization;
namespace Flowsharp.Activities
{
public abstract class Activity : IActivity
{
public virtual string Name => GetType().Name;
public Task ProvideMetadataAsync(ActivityMetadataContext context, CancellationToken cancellationToken)
{
ProvideMetadata(context);
return Task.CompletedTask;
}
public virtual Task<bool> CanExecuteAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public virtual Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(workflowContext, activityContext));
}
public virtual Task<ActivityExecutionResult> ResumeAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken)
{
return Task.FromResult(Resume(workflowContext, activityContext));
}
public virtual IEnumerable<Outcome> GetOutcomes(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
return Enumerable.Empty<Outcome>();
}
public virtual Task OnActivityExecutedAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken)
{
OnActivityExecuted(workflowContext, activityContext);
return Task.CompletedTask;
}
public virtual Task OnActivityExecutingAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken)
{
OnActivityExecuting(workflowContext, activityContext);
return Task.CompletedTask;
}
public virtual Task ReceiveInputAsync(WorkflowExecutionContext workflowContext, IDictionary<string, object> input, CancellationToken cancellationToken)
{
ReceiveInput(workflowContext, input);
return Task.CompletedTask;
}
public virtual Task WorkflowResumedAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
WorkflowResumed(workflowContext);
return Task.CompletedTask;
}
public virtual Task WorkflowResumingAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
WorkflowResuming(workflowContext);
return Task.CompletedTask;
}
public virtual Task WorkflowStartedAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
WorkflowStarted(workflowContext);
return Task.CompletedTask;
}
public virtual Task WorkflowStartingAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
WorkflowStarting(workflowContext);
return Task.CompletedTask;
}
protected virtual void ProvideMetadata(ActivityMetadataContext context)
{
}
protected virtual ActivityExecutionResult Execute(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
return Noop();
}
protected virtual ActivityExecutionResult Resume(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
return Noop();
}
protected virtual void OnActivityExecuted(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext) {}
protected virtual void OnActivityExecuting(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext) {}
protected virtual void ReceiveInput(WorkflowExecutionContext workflowContext, IDictionary<string, object> input) {}
protected virtual void WorkflowResumed(WorkflowExecutionContext workflowContext) {}
protected virtual void WorkflowResuming(WorkflowExecutionContext workflowContext) {}
protected virtual void WorkflowStarted(WorkflowExecutionContext workflowContext) {}
protected virtual void WorkflowStarting(WorkflowExecutionContext workflowContext) {}
protected IEnumerable<Outcome> Outcomes(params LocalizedString[] names)
{
return names.Select(x => new Outcome(x));
}
protected IEnumerable<Outcome> Outcomes(IEnumerable<LocalizedString> names)
{
return names.Select(x => new Outcome(x));
}
protected ActivityExecutionResult Outcomes(params string[] names)
{
return Outcomes((IEnumerable<string>)names);
}
protected ActivityExecutionResult Outcomes(IEnumerable<string> names)
{
return new OutcomeResult(names);
}
protected ActivityExecutionResult Halt()
{
return new HaltResult();
}
protected ActivityExecutionResult Noop()
{
return new NoopResult();
}
}
}

View file

@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
namespace Flowsharp.Activities
{
public interface IActivity
{
/// <summary>
/// The system name of the activity.
/// </summary>
/// <remarks>The Name is used to identify a given activity type and must be unique.</remarks>
string Name { get; }
/// <summary>
/// Provides metadata about the specified activity.
/// </summary>
Task ProvideMetadataAsync(ActivityMetadataContext context, CancellationToken cancellationToken);
/// <summary>
/// Returns a list of possible outcomes when the activity is executed.
/// </summary>
IEnumerable<Outcome> GetOutcomes(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext);
/// <summary>
/// Returns a value of whether the specified activity can execute.
/// </summary>
Task<bool> CanExecuteAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken);
/// <summary>
/// Executes the specified activity.
/// </summary>
Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken);
/// <summary>
/// Resumes the specified activity.
/// </summary>
Task<ActivityExecutionResult> ResumeAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken);
/// <summary>
/// Executes before a workflow starts or resumes, giving activities an opportunity to read and store any values of interest.
/// </summary>
Task ReceiveInputAsync(WorkflowExecutionContext workflowContext, IDictionary<string, object> input, CancellationToken cancellationToken);
/// <summary>
/// Executes when a workflow is about to start.
/// </summary>
Task WorkflowStartingAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken);
/// <summary>
/// Executes when a workflow has started.
/// </summary>
Task WorkflowStartedAsync(WorkflowExecutionContext context, CancellationToken cancellationToken);
/// <summary>
/// Executes when a workflow is about to be resumed.
/// </summary>
Task WorkflowResumingAsync(WorkflowExecutionContext context, CancellationToken cancellationToken);
/// <summary>
/// Executes when a workflow is resumed.
/// </summary>
Task WorkflowResumedAsync(WorkflowExecutionContext context, CancellationToken cancellationToken);
/// <summary>
/// Executes when an activity is about to be executed.
/// </summary>
/// <param name="workflowContext">The workflow execution context.</param>
/// <param name="activityContext">The activity context containing the activity that is the subject of the event.</param>
/// <param name="cancellationToken">The cancellation token.</param>
Task OnActivityExecutingAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Called on each activity when an activity has been executed.
/// </summary>
/// <param name="workflowContext">The workflow execution context.</param>
/// <param name="activityContext">The activity context containing the activity that is the subject of the event.</param>
/// <param name="cancellationToken">The cancellation token.</param>
Task OnActivityExecutedAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities;
using Flowsharp.Descriptors;
using Flowsharp.Services;
namespace Flowsharp.ActivityProviders
{
/// <summary>
/// Provides activities based on <see cref="IActivity"/> implementations that have been registered with the service container.
/// </summary>
public class TypedActivityProvider : IActivityProvider
{
private readonly Func<IEnumerable<IActivity>> _activitiesFactory;
public TypedActivityProvider(Func<IEnumerable<IActivity>> activitiesFactory)
{
_activitiesFactory = activitiesFactory;
}
public Task<IEnumerable<ActivityDescriptor>> GetActivityDescriptorsAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_activitiesFactory().Select(ToDescriptor));
}
private ActivityDescriptor ToDescriptor(IActivity activity)
{
return new ActivityDescriptor
{
Name = activity.Name,
GetMetadataAsync = activity.ProvideMetadataAsync,
CanExecuteAsync = activity.CanExecuteAsync,
GetOutcomes = activity.GetOutcomes,
ExecuteActivityAsync = activity.ExecuteAsync,
ResumeActivityAsync = activity.ResumeAsync,
ReceiveInputAsync = activity.ReceiveInputAsync,
WorkflowResumedAsync = activity.WorkflowResumedAsync,
WorkflowResumingAsync = activity.WorkflowResumingAsync,
WorkflowStartedAsync = activity.WorkflowStartedAsync,
WorkflowStartingAsync = activity.WorkflowStartingAsync,
OnActivityExecutedAsync = activity.OnActivityExecutedAsync,
OnActivityExecutingAsync = activity.OnActivityExecutingAsync
};
}
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
public abstract class ActivityExecutionResult : IActivityExecutionResult
{
public virtual Task ExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
Execute(workflowContext);
return Task.CompletedTask;
}
protected virtual void Execute(WorkflowExecutionContext workflowContext)
{
throw new NotImplementedException("You must either implement ExecuteAsync or Execute");
}
}
}

View file

@ -0,0 +1,31 @@
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
/// <summary>
/// Halts workflow execution.
/// </summary>
public class HaltResult : ActivityExecutionResult
{
public override async Task ExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
var currentActivity = workflowContext.CurrentExecutingActivity;
if (workflowContext.IsFirstPass)
{
// Resume immediately when this is the first pass.
var result = await currentActivity.ActivityDescriptor.ResumeActivityAsync(workflowContext, currentActivity, cancellationToken);
workflowContext.IsFirstPass = false;
await result.ExecuteAsync(workflowContext, cancellationToken);
}
else
{
// Block on this activity.
workflowContext.BlockingActivities.Add(currentActivity);
}
}
}
}

View file

@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
public interface IActivityExecutionResult
{
Task ExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,15 @@
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
/// <summary>
/// A result that does nothing.
/// </summary>
public class NoopResult : ActivityExecutionResult
{
protected override void Execute(WorkflowExecutionContext workflowContext)
{
// Noop.
}
}
}

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
/// <summary>
/// An activity execution result that sets the next outcomes to execute.
/// </summary>
public class OutcomeResult : ActivityExecutionResult
{
private readonly IEnumerable<string> outcomeNames;
public OutcomeResult(IEnumerable<string> names)
{
outcomeNames = names.ToList();
}
protected override void Execute(WorkflowExecutionContext workflowContext)
{
var workflowType = workflowContext.WorkflowType;
var currentActivity = workflowContext.CurrentExecutingActivity;
foreach (var outcome in outcomeNames)
{
// Look for next activity in the graph.
var transition = workflowType.Transitions.FirstOrDefault(x => x.From.ActivityId == currentActivity.ActivityType.Id && x.From.OutcomeName == outcome);
if (transition != null)
{
var destinationActivity = workflowContext.Activities.Values.Single(x => x.ActivityType.Id == transition.To.ActivityId);
workflowContext.PushScheduledActivity(destinationActivity);
}
}
}
}
}

View file

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
namespace Flowsharp.Descriptors
{
public class ActivityDescriptor
{
public string Name { get; set; }
/// <summary>
/// Provides metadata about the specified activity.
/// </summary>
public Func<ActivityMetadataContext, CancellationToken, Task> GetMetadataAsync { get; set; }
/// <summary>
/// Returns a list of possible outcomes when the activity is executed.
/// </summary>
public Func<WorkflowExecutionContext, ActivityExecutionContext, IEnumerable<Outcome>> GetOutcomes { get; set; }
/// <summary>
/// Returns a value of whether the specified activity can execute.
/// </summary>
public Func<WorkflowExecutionContext, ActivityExecutionContext, CancellationToken, Task<bool>> CanExecuteAsync { get; set; }
/// <summary>
/// Executes the specified activity.
/// </summary>
public Func<WorkflowExecutionContext, ActivityExecutionContext, CancellationToken, Task<ActivityExecutionResult>> ExecuteActivityAsync { get; set; }
/// <summary>
/// Resumes the specified activity.
/// </summary>
public Func<WorkflowExecutionContext, ActivityExecutionContext, CancellationToken, Task<ActivityExecutionResult>> ResumeActivityAsync { get; set; }
/// <summary>
/// Executes before a workflow starts or resumes, giving activities an opportunity to read and store any values of interest.
/// </summary>
public Func<WorkflowExecutionContext, IDictionary<string, object>, CancellationToken, Task> ReceiveInputAsync { get; set; }
/// <summary>
/// Executes when a workflow is about to start.
/// </summary>
public Func<WorkflowExecutionContext, CancellationToken, Task> WorkflowStartingAsync { get; set; }
/// <summary>
/// Executes when a workflow has started.
/// </summary>
public Func<WorkflowExecutionContext, CancellationToken, Task> WorkflowStartedAsync { get; set; }
/// <summary>
/// Executes when a workflow is about to be resumed.
/// </summary>
public Func<WorkflowExecutionContext, CancellationToken, Task> WorkflowResumingAsync { get; set; }
/// <summary>
/// Executes when a workflow is resumed.
/// </summary>
public Func<WorkflowExecutionContext, CancellationToken, Task> WorkflowResumedAsync { get; set; }
/// <summary>
/// Executes when an activity is about to be executed.
/// </summary>
/// <param name="activity">The activity for which the event is invoked. This is not necessarily the activity that is about to be executed.</param>
/// <param name="activityContext">The activity context containing the activity that is the subject of the event.</param>
public Func<WorkflowExecutionContext, ActivityExecutionContext, CancellationToken, Task> OnActivityExecutingAsync { get; set; }
/// <summary>
/// Called on each activity when an activity has been executed.
/// </summary>
/// <param name="activity">The activity for which the event is invoked. This is not necessarily the activity that is about to be executed.</param>
/// <param name="activityContext">The activity context containing the activity that is the subject of the event.</param>
public Func<WorkflowExecutionContext, ActivityExecutionContext, CancellationToken, Task> OnActivityExecutedAsync { get; set; }
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace Flowsharp.Extensions
{
public static class ExceptionExtensions
{
public static bool IsFatal(this Exception ex)
{
return
ex is OutOfMemoryException ||
ex is SecurityException ||
ex is SEHException;
}
}
}

View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Flowsharp.Extensions
{
public static class InvokeExtensions
{
/// <summary>
/// Safely invoke methods by catching non fatal exceptions and logging them.
/// </summary>
public static void Invoke<TEvents>(this IEnumerable<TEvents> events, Action<TEvents> dispatch, ILogger logger)
{
foreach (var sink in events)
{
try
{
dispatch(sink);
}
catch (Exception ex)
{
HandleException(ex, logger, typeof(TEvents).Name, sink.GetType().FullName);
}
}
}
/// <summary>
/// Safely invoke methods by catching non fatal exceptions and logging them.
/// </summary>
public static async Task InvokeAsync<TEvents>(this IEnumerable<TEvents> events, Func<TEvents, Task> dispatch, ILogger logger)
{
foreach (var sink in events)
{
try
{
await dispatch(sink);
}
catch (Exception ex)
{
HandleException(ex, logger, typeof(TEvents).Name, sink.GetType().FullName);
}
}
}
public static void HandleException(Exception ex, ILogger logger, string sourceType, string method)
{
if (ex.IsFatal())
throw ex;
logger.LogError(ex, "{Type} thrown from {Method} by {Exception}",
sourceType,
method,
ex.GetType().Name);
}
}
}

View file

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Flowsharp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,30 @@
using System;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
namespace Flowsharp.Json
{
/// <summary>
/// Serializes the <see cref="LocalizedString"/> to a simple string using the translated text.
/// </summary>
public class LocalizedStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(LocalizedString);
}
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var localizedString = (LocalizedString)value;
writer.WriteValue(localizedString.Value);
}
}
}

View file

@ -0,0 +1,19 @@
using Flowsharp.Descriptors;
using Newtonsoft.Json.Linq;
namespace Flowsharp.Models
{
public class ActivityExecutionContext
{
public ActivityExecutionContext(ActivityType activityType, ActivityDescriptor activityDescriptor)
{
ActivityType = activityType;
ActivityDescriptor = activityDescriptor;
State = new JObject(activityType.State);
}
public ActivityType ActivityType { get; }
public ActivityDescriptor ActivityDescriptor { get; }
public JObject State { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using Microsoft.Extensions.Localization;
namespace Flowsharp.Abstractions.Models
{
public class ActivityMetadata
{
public LocalizedString DisplayName { get; set; }
public LocalizedString Category { get; set; }
}
}

View file

@ -0,0 +1,9 @@
using Flowsharp.Abstractions.Models;
namespace Flowsharp.Models
{
public class ActivityMetadataContext
{
public ActivityMetadata Metadata { get; set; }
}
}

View file

@ -0,0 +1,18 @@
using Newtonsoft.Json.Linq;
namespace Flowsharp.Models
{
public class ActivityType
{
public ActivityType(string id, string name, JObject state = null)
{
Id = id;
Name = name;
State = state ?? new JObject();
}
public string Id { get; }
public string Name { get; }
public JObject State { get; }
}
}

View file

@ -0,0 +1,6 @@
namespace Flowsharp.Models
{
public class DestinationEndpoint : Endpoint
{
}
}

View file

@ -0,0 +1,7 @@
namespace Flowsharp.Models
{
public class Endpoint
{
public string ActivityId { get; set; }
}
}

View file

@ -0,0 +1,24 @@
using Flowsharp.Json;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
namespace Flowsharp.Models
{
public class Outcome
{
public Outcome(LocalizedString displayName) : this(displayName.Name, displayName)
{
}
public Outcome(string name, LocalizedString displayName)
{
Name = name;
DisplayName = displayName;
}
public string Name { get; }
[JsonConverter(typeof(LocalizedStringConverter))]
public LocalizedString DisplayName { get; }
}
}

View file

@ -0,0 +1,7 @@
namespace Flowsharp.Models
{
public class SourceEndpoint : Endpoint
{
public string OutcomeName { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Flowsharp.Models
{
public class Transition
{
public SourceEndpoint From { get; set; }
public DestinationEndpoint To { get; set; }
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Flowsharp.Descriptors;
namespace Flowsharp.Models
{
public class WorkflowExecutionContext
{
public WorkflowExecutionContext(IDictionary<string, ActivityDescriptor> activityDescriptorDictionary, WorkflowType workflowType, WorkflowStatus status)
{
WorkflowType = workflowType;
Activities = workflowType.Activities.ToDictionary(x => x.Id, x => new ActivityExecutionContext(x, activityDescriptorDictionary[x.Name]));
BlockingActivities = new List<ActivityExecutionContext>();
Status = status;
IsFirstPass = true;
scheduledActivities = new Stack<ActivityExecutionContext>();
}
private readonly Stack<ActivityExecutionContext> scheduledActivities;
public WorkflowType WorkflowType { get; }
public IDictionary<string, ActivityExecutionContext> Activities { get; }
public ICollection<ActivityExecutionContext> BlockingActivities { get; }
public WorkflowStatus Status { get; set; }
public bool HasScheduledActivities => scheduledActivities.Any();
public bool IsFirstPass { get; set; }
public ActivityExecutionContext CurrentExecutingActivity { get; private set; }
public void PushScheduledActivity(ActivityExecutionContext activityExecutionContext)
{
scheduledActivities.Push(activityExecutionContext);
}
public ActivityExecutionContext PopScheduledActivity()
{
return CurrentExecutingActivity = scheduledActivities.Pop();
}
public void Fault(Exception exception, ActivityExecutionContext activity)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,14 @@
namespace Flowsharp.Models
{
public enum WorkflowStatus
{
Idle,
Starting,
Resuming,
Executing,
Halted,
Finished,
Faulted,
Aborted
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Flowsharp.Models
{
public class WorkflowType
{
public string Name { get; set; }
public ICollection<ActivityType> Activities { get; set; }
public ICollection<Transition> Transitions { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Descriptors;
namespace Flowsharp.Services
{
public interface IActivityLibrary
{
Task<IEnumerable<ActivityDescriptor>> GetActivityDescriptorsAsync(CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Descriptors;
namespace Flowsharp.Services
{
/// <summary>
/// Implementors provide available activity descriptors.
/// </summary>
public interface IActivityProvider
{
Task<IEnumerable<ActivityDescriptor>> GetActivityDescriptorsAsync(CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Models;
namespace Flowsharp.Services
{
public interface IWorkflowInvoker
{
Task InvokeAsync(WorkflowExecutionContext workflowContext, string startActivityId, CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,19 @@
using System;
using Flowsharp.Activities;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
namespace Flowsharp.ActivityProviders
{
/// <summary>
/// Provides activities based on <see cref="IActivity"/> implementations that have been registered with the service container.
/// </summary>
public class WriteLine : Activity
{
protected override ActivityExecutionResult Execute(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
Console.WriteLine("Hello World!");
return Outcomes("Done");
}
}
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Flowsharp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Flowsharp.Abstractions\Flowsharp.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="ActivityResults" />
<Folder Include="Models\" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Descriptors;
namespace Flowsharp.Services
{
public class ActivityLibrary : IActivityLibrary
{
private readonly IEnumerable<IActivityProvider> providers;
public ActivityLibrary(IEnumerable<IActivityProvider> providers)
{
this.providers = providers;
}
public async Task<IEnumerable<ActivityDescriptor>> GetActivityDescriptorsAsync(CancellationToken cancellationToken)
{
var tasks = providers.Select(x => x.GetActivityDescriptorsAsync(cancellationToken)).ToList();
var results = await Task.WhenAll(tasks);
return results.SelectMany(x => x);
}
}
public static class ActivityLibraryExtensions
{
public static async Task<IDictionary<string, ActivityDescriptor>> GetActivityDescriptorDictionaryAsync(this IActivityLibrary activityLibrary, CancellationToken cancellationToken)
{
var activityDescriptors = await activityLibrary.GetActivityDescriptorsAsync(cancellationToken);
return activityDescriptors.ToDictionary(x => x.Name);
}
}
}

View file

@ -0,0 +1,99 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.ActivityResults;
using Flowsharp.Extensions;
using Flowsharp.Models;
using Microsoft.Extensions.Logging;
namespace Flowsharp.Services
{
public class WorkflowInvoker : IWorkflowInvoker
{
public WorkflowInvoker(ILogger<WorkflowInvoker> logger)
{
this.logger = logger;
}
private readonly ILogger logger;
public async Task InvokeAsync(WorkflowExecutionContext workflowContext, string startActivityId, CancellationToken cancellationToken)
{
var isResuming = workflowContext.Status == WorkflowStatus.Resuming;
var startActivity = workflowContext.Activities[startActivityId];
workflowContext.Status = WorkflowStatus.Executing;
workflowContext.PushScheduledActivity(startActivity);
while (workflowContext.HasScheduledActivities)
{
var currentActivity = workflowContext.PopScheduledActivity();
if (!await ExecuteActivityAsync(workflowContext, currentActivity, isResuming, cancellationToken))
break;
workflowContext.IsFirstPass = false;
isResuming = false;
}
workflowContext.Status = workflowContext.BlockingActivities.Any() ? WorkflowStatus.Halted : WorkflowStatus.Finished;
}
private async Task<bool> ExecuteActivityAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, bool isResuming, CancellationToken cancellationToken)
{
try
{
await InvokeActivitiesAsync(workflowContext, x => x.ActivityDescriptor.OnActivityExecutingAsync(workflowContext, activityContext, cancellationToken));
if (cancellationToken.IsCancellationRequested)
{
workflowContext.Status = WorkflowStatus.Aborted;
return false;
}
var result = await ExecuteOrResumeActivityAsync(workflowContext, activityContext, isResuming, cancellationToken);
await InvokeActivitiesAsync(workflowContext, x => x.ActivityDescriptor.OnActivityExecutedAsync(workflowContext, activityContext, cancellationToken));
await result.ExecuteAsync(workflowContext, cancellationToken);
}
catch (Exception ex)
{
FaultWorkflow(workflowContext, activityContext, ex);
}
return true;
}
private void FaultWorkflow(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, Exception ex)
{
logger.LogError(
ex,
"An unhandled error occurred while executing an activity. Workflow ID: '{WorkflowTypeId}'. Activity: '{ActivityId}', '{ActivityName}'. Putting the workflow in the faulted state.",
workflowContext.WorkflowType.Name,
activityContext.ActivityType.Id,
activityContext.ActivityType.Name
);
workflowContext.Fault(ex, activityContext);
}
private async Task<ActivityExecutionResult> ExecuteOrResumeActivityAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activity, bool isResuming, CancellationToken cancellationToken)
{
if (!isResuming)
{
// Execute the current activity.
return await activity.ActivityDescriptor.ExecuteActivityAsync(workflowContext, activity, cancellationToken);
}
else
{
// Resume the current activity.
return await activity.ActivityDescriptor.ResumeActivityAsync(workflowContext, activity, cancellationToken);
}
}
private async Task InvokeActivitiesAsync(WorkflowExecutionContext workflowContext, Func<ActivityExecutionContext, Task> action)
{
await workflowContext.Activities.Values.InvokeAsync(action, logger);
}
}
}

View file

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Flowsharp.Abstractions\Flowsharp.Abstractions.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using Flowsharp.Models;
namespace Flowsharp.Fluid
{
public class WorkflowBuilder
{
public WorkflowBuilder()
{
activityTypes = new List<ActivityType>();
}
private IList<ActivityType> activityTypes;
}
}

View file

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Flowsharp.Abstractions\Flowsharp.Abstractions.csproj" />
<ProjectReference Include="..\Flowsharp.Core\Flowsharp.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,34 @@
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities;
using Flowsharp.ActivityProviders;
using Flowsharp.Models;
using Flowsharp.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Flowsharp.Samples.Console
{
class Program
{
static async Task Main()
{
var workflowType = new WorkflowType
{
Activities = new[]
{
new ActivityType("1", "WriteLine")
},
Transitions = new Transition[0]
};
var typedActivityProvider = new TypedActivityProvider(() => new IActivity[] { new WriteLine() } );
var activityLibrary = new ActivityLibrary(new[]{ typedActivityProvider });
var dictionary = await activityLibrary.GetActivityDescriptorDictionaryAsync(CancellationToken.None);
var workflowContext = new WorkflowExecutionContext(dictionary, workflowType, WorkflowStatus.Idle);
var invoker = new WorkflowInvoker(new Logger<WorkflowInvoker>(new NullLoggerFactory()));
await invoker.InvokeAsync(workflowContext, "1", CancellationToken.None);
}
}
}