Removing nested mode

This commit is contained in:
Sipke Schoorstra 2018-10-14 12:24:36 +02:00
parent 1635618b01
commit 3b9d5e616e
48 changed files with 616 additions and 504 deletions

View file

@ -16,10 +16,9 @@ ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
README.md = README.md
Nuget.Config = Nuget.Config
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
@ -38,10 +37,6 @@ Global
{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
@ -53,6 +48,5 @@ Global
{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

6
Nuget.Config Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -1,26 +1,16 @@
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);
return Task.FromResult(CanExecute(workflowContext, activityContext));
}
public virtual Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken)
@ -33,11 +23,6 @@ namespace Flowsharp.Activities
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);
@ -80,20 +65,17 @@ namespace Flowsharp.Activities
return Task.CompletedTask;
}
protected virtual void ProvideMetadata(ActivityMetadataContext context)
{
}
protected virtual ActivityExecutionResult Execute(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
return Noop();
return ActivateEndpoint();
}
protected virtual ActivityExecutionResult Resume(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
return Noop();
return ActivateEndpoint();
}
protected virtual bool CanExecute(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext) => true;
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) {}
@ -102,32 +84,32 @@ namespace Flowsharp.Activities
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()
protected HaltResult Halt()
{
return new HaltResult();
}
protected ActivityExecutionResult Noop()
protected ActivateEndpointResult ActivateEndpoint(string name = null)
{
return new ActivateEndpointResult(new SourceEndpoint(this, name));
}
protected ScheduleActivityResult ScheduleActivity(IActivity activity)
{
return new ScheduleActivityResult(activity);
}
protected ReturnValueResult SetReturnValue(object value)
{
return new ReturnValueResult(value);
}
protected FinishWorkflowResult Finish()
{
return new FinishWorkflowResult();
}
protected NoopResult Noop()
{
return new NoopResult();
}

View file

@ -8,22 +8,6 @@ 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>

View file

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
namespace Flowsharp.Activities
{
public class Workflow
{
public Workflow()
{
}
public Workflow(IEnumerable<IActivity> activities, IEnumerable<Connection> connections)
{
Activities = activities.ToList();
Connections = connections.ToList();
}
public IList<IActivity> Activities { get; set; } = new List<IActivity>();
public IList<Connection> Connections { get; set; } = new List<Connection>();
}
}

View file

@ -1,49 +0,0 @@
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,22 @@
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
/// <summary>
/// A result that carries information about the next activity to execute.
/// </summary>
public class ActivateEndpointResult : ActivityExecutionResult
{
public ActivateEndpointResult(SourceEndpoint endpoint)
{
Endpoint = endpoint;
}
public SourceEndpoint Endpoint { get; }
protected override void Execute(WorkflowExecutionContext workflowContext)
{
workflowContext.ScheduleNextActivities(workflowContext, Endpoint);
}
}
}

View file

@ -0,0 +1,12 @@
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
public class FinishWorkflowResult : ActivityExecutionResult
{
protected override void Execute(WorkflowExecutionContext workflowContext)
{
workflowContext.Finish();
}
}
}

View file

@ -11,20 +11,19 @@ namespace Flowsharp.ActivityResults
{
public override async Task ExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
var currentActivity = workflowContext.CurrentExecutingActivity;
var currentActivity = workflowContext.CurrentActivity;
if (workflowContext.IsFirstPass)
{
// Resume immediately when this is the first pass.
var result = await currentActivity.ActivityDescriptor.ResumeActivityAsync(workflowContext, currentActivity, cancellationToken);
var result = await currentActivity.ResumeAsync(workflowContext, new ActivityExecutionContext(currentActivity), cancellationToken);
workflowContext.IsFirstPass = false;
await result.ExecuteAsync(workflowContext, cancellationToken);
}
else
{
// Block on this activity.
workflowContext.BlockingActivities.Add(currentActivity);
await workflowContext.HaltAsync(cancellationToken);
}
}
}

View file

@ -1,37 +0,0 @@
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,19 @@
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
public class ReturnValueResult : ActivityExecutionResult
{
private readonly object value;
public ReturnValueResult(object value)
{
this.value = value;
}
protected override void Execute(WorkflowExecutionContext workflowContext)
{
workflowContext.SetReturnValue(value);
}
}
}

View file

@ -0,0 +1,20 @@
using Flowsharp.Activities;
using Flowsharp.Models;
namespace Flowsharp.ActivityResults
{
public class ScheduleActivityResult : ActivityExecutionResult
{
private readonly IActivity activity;
public ScheduleActivityResult(IActivity activity)
{
this.activity = activity;
}
protected override void Execute(WorkflowExecutionContext workflowContext)
{
workflowContext.ScheduleActivity(activity);
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using Flowsharp.Activities;
using Flowsharp.Models;
namespace Flowsharp.Builders
{
public class ActivityWorkflowBuilder
{
private readonly IActivity activity;
private readonly WorkflowBuilder builder;
public ActivityWorkflowBuilder(WorkflowBuilder builder, IActivity activity)
{
this.builder = builder;
this.activity = activity;
}
public ActivityWorkflowBuilder Connect(IActivity target, Action<ActivityWorkflowBuilder> activityBuilder = null)
{
var connection = new Connection(activity, target);
builder.Activities.Add(target);
builder.Connections.Add(connection);
activityBuilder?.Invoke(new ActivityWorkflowBuilder(builder, target));
return this;
}
}
}

View file

@ -0,0 +1,32 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Flowsharp.Activities;
using Flowsharp.Models;
namespace Flowsharp.Builders
{
public class WorkflowBuilder
{
public WorkflowBuilder()
{
Activities = new List<IActivity>();
Connections = new List<Connection>();
}
public IList<IActivity> Activities { get; }
public IList<Connection> Connections { get; }
public WorkflowBuilder AddActivity(IActivity activity, Action<ActivityWorkflowBuilder> builder = null)
{
Activities.Add(activity);
builder?.Invoke(new ActivityWorkflowBuilder(this, activity));
return this;
}
public Workflow Build()
{
return new Workflow(Activities, Connections);
}
}
}

View file

@ -1,78 +0,0 @@
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,9 @@
using Flowsharp.Activities;
using Flowsharp.Models;
namespace Flowsharp.Extensions
{
public static class ConnectionActivityExtensions
{
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Flowsharp.Activities;
using Microsoft.Extensions.Logging;
namespace Flowsharp.Extensions
@ -10,7 +11,7 @@ namespace Flowsharp.Extensions
/// <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)
public static void Invoke<T>(this IEnumerable<T> events, Action<T> dispatch, ILogger logger)
{
foreach (var sink in events)
{
@ -20,7 +21,7 @@ namespace Flowsharp.Extensions
}
catch (Exception ex)
{
HandleException(ex, logger, typeof(TEvents).Name, sink.GetType().FullName);
HandleException(ex, logger, typeof(T).Name, sink.GetType().FullName);
}
}
}
@ -28,7 +29,7 @@ namespace Flowsharp.Extensions
/// <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)
public static async Task InvokeAsync<T>(this IEnumerable<T> events, Func<T, Task> dispatch, ILogger logger)
{
foreach (var sink in events)
{
@ -38,12 +39,12 @@ namespace Flowsharp.Extensions
}
catch (Exception ex)
{
HandleException(ex, logger, typeof(TEvents).Name, sink.GetType().FullName);
HandleException(ex, logger, typeof(T).Name, sink.GetType().FullName);
}
}
}
public static void HandleException(Exception ex, ILogger logger, string sourceType, string method)
private static void HandleException(Exception ex, ILogger logger, string sourceType, string method)
{
if (ex.IsFatal())
throw ex;

View file

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Flowsharp</RootNamespace>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>

View file

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

View file

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

View file

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

View file

@ -1,18 +0,0 @@
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,28 @@
using Flowsharp.Activities;
namespace Flowsharp.Models
{
public class Connection
{
public Connection()
{
}
public Connection(IActivity source, IActivity target) : this(new SourceEndpoint(source), new TargetEndpoint(target))
{
}
public Connection(IActivity source, string sourceEndpointName, IActivity target) : this(new SourceEndpoint(source, sourceEndpointName), new TargetEndpoint(target))
{
}
public Connection(SourceEndpoint source, TargetEndpoint target)
{
Source = source;
Target = target;
}
public SourceEndpoint Source { get; set; }
public TargetEndpoint Target { get; set; }
}
}

View file

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

View file

@ -1,7 +1,18 @@
namespace Flowsharp.Models
using Flowsharp.Activities;
namespace Flowsharp.Models
{
public class Endpoint
public abstract class Endpoint
{
public string ActivityId { get; set; }
protected Endpoint()
{
}
protected Endpoint(IActivity activity)
{
Activity = activity;
}
public IActivity Activity { get; set; }
}
}
}

View file

@ -1,24 +0,0 @@
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

@ -1,7 +1,18 @@
namespace Flowsharp.Models
using Flowsharp.Activities;
namespace Flowsharp.Models
{
public class SourceEndpoint : Endpoint
{
public string OutcomeName { get; set; }
public SourceEndpoint()
{
}
public SourceEndpoint(IActivity activity, string name = null) : base(activity)
{
Name = name;
}
public string Name { get; set; }
}
}
}

View file

@ -0,0 +1,15 @@
using Flowsharp.Activities;
namespace Flowsharp.Models
{
public class TargetEndpoint : Endpoint
{
public TargetEndpoint()
{
}
public TargetEndpoint(IActivity activity) : base(activity)
{
}
}
}

View file

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

View file

@ -1,46 +1,87 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Flowsharp.Descriptors;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities;
namespace Flowsharp.Models
{
public class WorkflowExecutionContext
{
public WorkflowExecutionContext(IDictionary<string, ActivityDescriptor> activityDescriptorDictionary, WorkflowType workflowType, WorkflowStatus status)
public WorkflowExecutionContext(Workflow workflow, WorkflowStatus status = WorkflowStatus.Idle)
{
WorkflowType = workflowType;
Activities = workflowType.Activities.ToDictionary(x => x.Id, x => new ActivityExecutionContext(x, activityDescriptorDictionary[x.Name]));
BlockingActivities = new List<ActivityExecutionContext>();
Workflow = workflow;
Status = status;
IsFirstPass = true;
IsFirstPass = true;
scheduledActivities = new Stack<IActivity>();
scopes = new Stack<WorkflowExecutionScope>();
scheduledActivities = new Stack<ActivityExecutionContext>();
BeginScope();
}
private readonly Stack<ActivityExecutionContext> scheduledActivities;
private readonly Stack<IActivity> scheduledActivities;
private readonly Stack<WorkflowExecutionScope> scopes;
public WorkflowType WorkflowType { get; }
public IDictionary<string, ActivityExecutionContext> Activities { get; }
public ICollection<ActivityExecutionContext> BlockingActivities { get; }
public Workflow Workflow { get; }
public WorkflowStatus Status { get; set; }
public bool HasScheduledActivities => scheduledActivities.Any();
public bool IsFirstPass { get; set; }
public ActivityExecutionContext CurrentExecutingActivity { get; private set; }
public IActivity CurrentActivity { get; private set; }
public WorkflowExecutionScope CurrentScope { get; private set; }
public void PushScheduledActivity(ActivityExecutionContext activityExecutionContext)
public void BeginScope()
{
scopes.Push(CurrentScope = new WorkflowExecutionScope());
}
public void EndScope()
{
scheduledActivities.Push(activityExecutionContext);
scopes.Pop();
CurrentScope = scopes.Peek();
}
public void ScheduleActivity(IActivity activity)
{
scheduledActivities.Push(activity);
}
public ActivityExecutionContext PopScheduledActivity()
public IActivity PopScheduledActivity()
{
return CurrentExecutingActivity = scheduledActivities.Pop();
CurrentActivity = scheduledActivities.Pop();
return CurrentActivity;
}
public void SetReturnValue(object value)
{
CurrentScope.ReturnValue = value;
}
public void Fault(Exception exception, ActivityExecutionContext activity)
public void Fault(Exception exception, IActivity activity)
{
throw new NotImplementedException();
}
public Task HaltAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public void Finish()
{
Status = WorkflowStatus.Finished;
}
public virtual void ScheduleNextActivities(WorkflowExecutionContext workflowContext, SourceEndpoint endpoint)
{
var completedActivity = workflowContext.CurrentActivity;
var connections = Workflow.Connections.Where(x => x.Source.Activity == completedActivity && x.Source.Name == endpoint.Name);
foreach (var connection in connections)
{
workflowContext.ScheduleActivity(connection.Target.Activity);
}
}
}
}

View file

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace Flowsharp.Models
{
public class WorkflowExecutionScope
{
public WorkflowExecutionScope()
{
Variables = new Dictionary<string, object>();
}
public object ReturnValue { get; set; }
public IDictionary<string, object> Variables { get; }
public void SetVariable(string variableName, object value)
{
Variables[variableName] = value;
}
public T GetVariable<T>(string name)
{
return Variables.ContainsKey(name) ? (T)Variables[name] : default(T);
}
}
}

View file

@ -1,11 +0,0 @@
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,11 @@
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities;
namespace Flowsharp.Services
{
public interface IWorkflowSerializer
{
Task<string> SerializeAsync(Workflow workflow, CancellationToken cancellationToken);
}
}

View file

@ -1,12 +0,0 @@
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

@ -1,15 +0,0 @@
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

@ -1,11 +1,12 @@
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities;
using Flowsharp.Models;
namespace Flowsharp.Services
{
public interface IWorkflowInvoker
{
Task InvokeAsync(WorkflowExecutionContext workflowContext, string startActivityId, CancellationToken cancellationToken);
Task<WorkflowExecutionContext> InvokeAsync(Workflow workflow, IActivity startActivity = default, CancellationToken cancellationToken = default);
}
}

View file

@ -0,0 +1,37 @@
using System;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
namespace Flowsharp.Activities
{
public class IfElse : Activity
{
private readonly Func<WorkflowExecutionContext, ActivityExecutionContext, bool> condition;
public IfElse()
{
}
public IfElse(Func<WorkflowExecutionContext, ActivityExecutionContext, bool> condition, IActivity trueBranch, IActivity falseBranch)
{
this.condition = condition;
TrueBranch = trueBranch;
FalseBranch = falseBranch;
}
public IActivity TrueBranch { get; set; }
public IActivity FalseBranch { get; set;}
protected override ActivityExecutionResult Execute(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
var result = condition(workflowContext, activityContext);
if (TrueBranch != null && result)
return ScheduleActivity(TrueBranch);
if(FalseBranch != null && !result)
return ScheduleActivity(FalseBranch);
return Noop();
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
namespace Flowsharp.Activities
{
public class ReadLine : Activity
{
private readonly TextReader input;
public ReadLine() : this(Console.In)
{
}
public ReadLine(TextReader input)
{
this.input = input;
}
public override async Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken)
{
var value = await input.ReadLineAsync();
workflowContext.SetReturnValue(value);
return ActivateEndpoint();
}
}
}

View file

@ -0,0 +1,37 @@
using System;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
namespace Flowsharp.Activities
{
public class SetVariable : Activity
{
private readonly Func<WorkflowExecutionContext, ActivityExecutionContext, object> valueProvider;
public SetVariable()
{
}
public SetVariable(string name, Func<WorkflowExecutionContext, ActivityExecutionContext, object> valueProvider) : this(name, default(object))
{
VariableName = name;
this.valueProvider = valueProvider;
}
public SetVariable(string name, object value)
{
VariableName = name;
Value = value;
}
public string VariableName { get; set; }
public object Value { get; set; }
protected override ActivityExecutionResult Execute(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
var value = valueProvider(workflowContext, activityContext);
workflowContext.CurrentScope.SetVariable(VariableName, value);
return ActivateEndpoint();
}
}
}

View file

@ -1,19 +1,54 @@
using System;
using Flowsharp.Activities;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.ActivityResults;
using Flowsharp.Models;
namespace Flowsharp.ActivityProviders
namespace Flowsharp.Activities
{
/// <summary>
/// Provides activities based on <see cref="IActivity"/> implementations that have been registered with the service container.
/// Writes a text string to the specified stream.
/// </summary>
public class WriteLine : Activity
{
protected override ActivityExecutionResult Execute(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
private readonly TextWriter output;
private readonly Func<WorkflowExecutionContext, ActivityExecutionContext, string> textProvider;
public WriteLine() : this(Console.Out, null)
{
Console.WriteLine("Hello World!");
return Outcomes("Done");
}
public WriteLine(string text) : this(Console.Out, text)
{
}
public WriteLine(Func<WorkflowExecutionContext, ActivityExecutionContext, string> textProvider) : this(Console.Out, null)
{
this.textProvider = textProvider;
}
public WriteLine(TextWriter output, string text)
{
this.output = output;
Text = text;
textProvider = (w, a) => Text;
}
public string Text { get; set; }
protected override bool CanExecute(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext)
{
var text = textProvider(workflowContext, activityContext);
return text != null;
}
public override async Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, CancellationToken cancellationToken)
{
var text = textProvider(workflowContext, activityContext);
await output.WriteLineAsync(text);
return ActivateEndpoint();
}
}
}

View file

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Flowsharp</RootNamespace>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>
@ -14,4 +15,8 @@
<Folder Include="Models\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="YamlDotNet.NetStandard" Version="4.0.0" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,22 @@
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities;
using Flowsharp.Services;
using Newtonsoft.Json;
namespace Flowsharp.Serialization
{
public class JsonWorkflowSerializer : IWorkflowSerializer
{
public Task<string> SerializeAsync(Workflow workflow, CancellationToken cancellationToken)
{
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
TypeNameHandling = TypeNameHandling.Objects
};
var json = JsonConvert.SerializeObject(workflow, settings);
return Task.FromResult(json);
}
}
}

View file

@ -1,35 +0,0 @@
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

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities;
using Flowsharp.ActivityResults;
using Flowsharp.Extensions;
using Flowsharp.Models;
@ -18,82 +20,87 @@ namespace Flowsharp.Services
private readonly ILogger logger;
public async Task InvokeAsync(WorkflowExecutionContext workflowContext, string startActivityId, CancellationToken cancellationToken)
public async Task<WorkflowExecutionContext> InvokeAsync(Workflow workflow, IActivity startActivity = default, CancellationToken cancellationToken = default)
{
var isResuming = workflowContext.Status == WorkflowStatus.Resuming;
var startActivity = workflowContext.Activities[startActivityId];
var workflowExecutionContext = new WorkflowExecutionContext(workflow);
var isResuming = workflowExecutionContext.Status == WorkflowStatus.Resuming;
workflowContext.Status = WorkflowStatus.Executing;
workflowContext.PushScheduledActivity(startActivity);
while (workflowContext.HasScheduledActivities)
if (startActivity == null)
startActivity = workflow.Activities.First();
workflowExecutionContext.Status = WorkflowStatus.Executing;
workflowExecutionContext.ScheduleActivity(startActivity);
await InvokeActivitiesAsync(workflowExecutionContext, x => x.WorkflowStartingAsync(workflowExecutionContext, cancellationToken));
while (workflowExecutionContext.HasScheduledActivities)
{
var currentActivity = workflowContext.PopScheduledActivity();
var currentActivity = workflowExecutionContext.PopScheduledActivity();
var result = await ExecuteActivityAsync(workflowExecutionContext, currentActivity, isResuming, cancellationToken);
if (!await ExecuteActivityAsync(workflowContext, currentActivity, isResuming, cancellationToken))
if(result == null)
break;
await result.ExecuteAsync(workflowExecutionContext, cancellationToken);
workflowContext.IsFirstPass = false;
workflowExecutionContext.IsFirstPass = false;
isResuming = false;
}
workflowContext.Status = workflowContext.BlockingActivities.Any() ? WorkflowStatus.Halted : WorkflowStatus.Finished;
workflowExecutionContext.Status = WorkflowStatus.Finished;
return workflowExecutionContext;
}
private async Task<bool> ExecuteActivityAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, bool isResuming, CancellationToken cancellationToken)
private async Task<ActivityExecutionResult> ExecuteActivityAsync(WorkflowExecutionContext workflowContext, IActivity activity, bool isResuming, CancellationToken cancellationToken)
{
try
{
await InvokeActivitiesAsync(workflowContext, x => x.ActivityDescriptor.OnActivityExecutingAsync(workflowContext, activityContext, cancellationToken));
//await InvokeActivitiesAsync(workflowContext, x => x.ActivityDescriptor.OnActivityExecutingAsync(workflowContext, activity, cancellationToken));
if (cancellationToken.IsCancellationRequested)
{
workflowContext.Status = WorkflowStatus.Aborted;
return false;
return null;
}
var result = await ExecuteOrResumeActivityAsync(workflowContext, activityContext, isResuming, cancellationToken);
await InvokeActivitiesAsync(workflowContext, x => x.ActivityDescriptor.OnActivityExecutedAsync(workflowContext, activityContext, cancellationToken));
await result.ExecuteAsync(workflowContext, cancellationToken);
return await ExecuteOrResumeActivityAsync(workflowContext, activity, isResuming, cancellationToken);
//await InvokeActivitiesAsync(workflowContext, x => x.ActivityDescriptor.OnActivityExecutedAsync(workflowContext, activity, cancellationToken));
}
catch (Exception ex)
{
FaultWorkflow(workflowContext, activityContext, ex);
FaultWorkflow(workflowContext, activity, ex);
}
return true;
return null;
}
private void FaultWorkflow(WorkflowExecutionContext workflowContext, ActivityExecutionContext activityContext, Exception ex)
private void FaultWorkflow(WorkflowExecutionContext workflowContext, IActivity activity, 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
"An unhandled error occurred while executing an activity. Putting the workflow in the faulted state."
);
workflowContext.Fault(ex, activityContext);
workflowContext.Fault(ex, activity);
}
private async Task<ActivityExecutionResult> ExecuteOrResumeActivityAsync(WorkflowExecutionContext workflowContext, ActivityExecutionContext activity, bool isResuming, CancellationToken cancellationToken)
private async Task<ActivityExecutionResult> ExecuteOrResumeActivityAsync(WorkflowExecutionContext workflowContext, IActivity activity, bool isResuming, CancellationToken cancellationToken)
{
if (!isResuming)
{
// Execute the current activity.
return await activity.ActivityDescriptor.ExecuteActivityAsync(workflowContext, activity, cancellationToken);
return await activity.ExecuteAsync(workflowContext, new ActivityExecutionContext(activity), cancellationToken);
}
else
{
// Resume the current activity.
return await activity.ActivityDescriptor.ResumeActivityAsync(workflowContext, activity, cancellationToken);
return await activity.ResumeAsync(workflowContext, new ActivityExecutionContext(activity), cancellationToken);
}
}
private async Task InvokeActivitiesAsync(WorkflowExecutionContext workflowContext, Func<ActivityExecutionContext, Task> action)
private async Task InvokeActivitiesAsync(WorkflowExecutionContext workflowContext, Func<IActivity, Task> action)
{
await workflowContext.Activities.Values.InvokeAsync(action, logger);
await workflowContext.Workflow.Activities.InvokeAsync(action, logger);
}
}
}

View file

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

View file

@ -1,17 +0,0 @@
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

@ -1,8 +1,9 @@
using System.Threading;
using System;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities;
using Flowsharp.ActivityProviders;
using Flowsharp.Models;
using Flowsharp.Builders;
using Flowsharp.Serialization;
using Flowsharp.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@ -13,22 +14,30 @@ namespace Flowsharp.Samples.Console
{
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()));
var workflow = new WorkflowBuilder()
.AddActivity(new WriteLine("You have now transitioned into a networked workflow."), helloWorld =>
helloWorld.Connect(new WriteLine("Let's run a program."), runProgram =>
runProgram.Connect(new WriteLine("Enter first value:"), firstValue =>
firstValue.Connect(new ReadLine(), value1 =>
value1.Connect(new SetVariable("x", (w, a) => int.Parse((string)w.CurrentScope.ReturnValue)), setX =>
setX.Connect(new WriteLine("Enter second value:"), secondValue =>
secondValue.Connect(new ReadLine(), value2 =>
value2.Connect(new SetVariable("y", (w, a) => int.Parse((string)w.CurrentScope.ReturnValue)), setY =>
setY.Connect(new WriteLine((w, a) =>
{
var x = w.CurrentScope.GetVariable<int>("x");
var y = w.CurrentScope.GetVariable<int>("y");
var z = x + y;
return $"{x} + {y} = {z}";
}))))))))))
.Build();
await invoker.InvokeAsync(workflowContext, "1", CancellationToken.None);
await invoker.InvokeAsync(workflow);
var serializer = new JsonWorkflowSerializer();
var json = await serializer.SerializeAsync(workflow, CancellationToken.None);
}
}
}