Skip to content

Commit

Permalink
Merge pull request #44 from danielgerlag/refineapi2
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgerlag authored Jun 24, 2017
2 parents 305e0be + e25d9c6 commit be4091f
Show file tree
Hide file tree
Showing 52 changed files with 978 additions and 268 deletions.
41 changes: 41 additions & 0 deletions 1.2.9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Workflow Core 1.2.9

* .Recur() API, define a block of steps to execute on a recurring interval, until a condition becomes true

This following example will execute the block of every day until the `StopRecurring` field on the data object becomes true
```c#
builder
.StartWith<HelloWorld>()
.Recur(data => TimeSpan.FromDays(1), data => data.StopRecurring)
.Do(recur => recur
.StartWith<DoSomething>()
.Then<DoSomethingElse>())
.Then<GoodbyeWorld>();
```

* Added access to the execution context object when resolving an event key in the .WaitFor API

```c#
.WaitFor("event", (data, context) => context.Workflow.Id)
```

* Deprecated `UserStep` in favour of the new `UserTask` API
* Added basic escalation functionality to `UserTask`

This following example will create a user task with 2 options and 2 resultant paths, as well as escalate the task after 1 day.
```c#
builder
.StartWith(context => ExecutionResult.Next())
.UserTask("Do you approve", data => @"domain\bob")
.WithOption("yes", "I approve").Do(then => then
.StartWith(context => Console.WriteLine("You approved"))
)
.WithOption("no", "I do not approve").Do(then => then
.StartWith(context => Console.WriteLine("You did not approve"))
)
.WithEscalation(x => TimeSpan.FromDays(1), x => @"domain\frank", action => action
.StartWith(context => Console.WriteLine("Escalated task"))
.Then(context => Console.WriteLine("Sending notification..."))
)
.Then(context => Console.WriteLine("end"));
```
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Workflow Core is a light weight workflow engine targeting .NET Standard. Think:

## Documentation

See [Full Documentation here.](https://github.com/danielgerlag/workflow-core/wiki)
See [Tutorial here.](https://github.com/danielgerlag/workflow-core/wiki)

## Fluent API

Expand Down
27 changes: 21 additions & 6 deletions WorkflowCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,28 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample09", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample10", "src\samples\WorkflowCore.Sample10\WorkflowCore.Sample10.csproj", "{5E792455-4C4C-460F-849E-50A5DCED454D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample11", "src\samples\WorkflowCore.Sample11\WorkflowCore.Sample11.csproj", "{58D0480F-D05D-4348-86D9-B0A7255700E6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample11", "src\samples\WorkflowCore.Sample11\WorkflowCore.Sample11.csproj", "{58D0480F-D05D-4348-86D9-B0A7255700E6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample12", "src\samples\WorkflowCore.Sample12\WorkflowCore.Sample12.csproj", "{BB776411-D279-419F-8697-5C6F52BCD5CD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample12", "src\samples\WorkflowCore.Sample12\WorkflowCore.Sample12.csproj", "{BB776411-D279-419F-8697-5C6F52BCD5CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.Sqlite", "test\WorkflowCore.Tests.Sqlite\WorkflowCore.Tests.Sqlite.csproj", "{F9F8F9CD-01D9-468B-856D-6A87F0762A01}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.Sqlite", "test\WorkflowCore.Tests.Sqlite\WorkflowCore.Tests.Sqlite.csproj", "{F9F8F9CD-01D9-468B-856D-6A87F0762A01}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.LockProviders.SqlServer", "src\providers\WorkflowCore.LockProviders.SqlServer\WorkflowCore.LockProviders.SqlServer.csproj", "{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.LockProviders.SqlServer", "src\providers\WorkflowCore.LockProviders.SqlServer\WorkflowCore.LockProviders.SqlServer.csproj", "{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample13", "src\samples\WorkflowCore.Sample13\WorkflowCore.Sample13.csproj", "{77C49ACA-203E-428C-A4DB-114DFE454988}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample13", "src\samples\WorkflowCore.Sample13\WorkflowCore.Sample13.csproj", "{77C49ACA-203E-428C-A4DB-114DFE454988}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Providers.Azure", "src\providers\WorkflowCore.Providers.Azure\WorkflowCore.Providers.Azure.csproj", "{A2374B7C-4198-40B3-B8FE-FAC3DB3F2539}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Providers.Azure", "src\providers\WorkflowCore.Providers.Azure\WorkflowCore.Providers.Azure.csproj", "{A2374B7C-4198-40B3-B8FE-FAC3DB3F2539}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReleaseNotes", "ReleaseNotes", "{38ECB00C-3F3B-4442-8408-ACE3B37FFAA8}"
ProjectSection(SolutionItems) = preProject
ReleaseNotes\1.2.8.md = ReleaseNotes\1.2.8.md
1.2.9.md = 1.2.9.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample14", "src\samples\WorkflowCore.Sample14\WorkflowCore.Sample14.csproj", "{6BC66637-B42A-4334-ADFB-DBEC9F29D293}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Testing", "test\WorkflowCore.Testing\WorkflowCore.Testing.csproj", "{62A9709E-27DA-42EE-B94F-5AF431D86354}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -239,6 +244,14 @@ Global
{A2374B7C-4198-40B3-B8FE-FAC3DB3F2539}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2374B7C-4198-40B3-B8FE-FAC3DB3F2539}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2374B7C-4198-40B3-B8FE-FAC3DB3F2539}.Release|Any CPU.Build.0 = Release|Any CPU
{6BC66637-B42A-4334-ADFB-DBEC9F29D293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BC66637-B42A-4334-ADFB-DBEC9F29D293}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BC66637-B42A-4334-ADFB-DBEC9F29D293}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BC66637-B42A-4334-ADFB-DBEC9F29D293}.Release|Any CPU.Build.0 = Release|Any CPU
{62A9709E-27DA-42EE-B94F-5AF431D86354}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62A9709E-27DA-42EE-B94F-5AF431D86354}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62A9709E-27DA-42EE-B94F-5AF431D86354}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62A9709E-27DA-42EE-B94F-5AF431D86354}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -282,5 +295,7 @@ Global
{AAE2E9F9-37EF-4AE1-A200-D37417C9040C} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{77C49ACA-203E-428C-A4DB-114DFE454988} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
{A2374B7C-4198-40B3-B8FE-FAC3DB3F2539} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{6BC66637-B42A-4334-ADFB-DBEC9F29D293} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
{62A9709E-27DA-42EE-B94F-5AF431D86354} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
EndGlobalSection
EndGlobal
5 changes: 5 additions & 0 deletions src/WorkflowCore/Interface/IContainerStepBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ public interface IContainerStepBuilder<TData, TStepBody, TReturnStep>
where TStepBody : IStepBody
where TReturnStep : IStepBody
{
/// <summary>
/// The block of steps to execute
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
IStepBuilder<TData, TReturnStep> Do(Action<IWorkflowBuilder<TData>> builder);
}
}
45 changes: 31 additions & 14 deletions src/WorkflowCore/Interface/IStepBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public interface IStepBuilder<TData, TStepBody>
/// <summary>
/// Specify the next step in the workflow
/// </summary>
/// <typeparam name="TStep"></typeparam>
/// <param name="stepSetup"></param>
/// <typeparam name="TStep">The type of the step to execute</typeparam>
/// <param name="stepSetup">Configure additional parameters for this step</param>
/// <returns></returns>
IStepBuilder<TData, TStep> Then<TStep>(Action<IStepBuilder<TData, TStep>> stepSetup = null) where TStep : IStepBody;

Expand Down Expand Up @@ -73,7 +73,7 @@ public interface IStepBuilder<TData, TStepBody>
/// Map properties on the step to properties on the workflow data object before the step executes
/// </summary>
/// <typeparam name="TInput"></typeparam>
/// <param name="stepProperty"></param>
/// <param name="stepProperty">The property on the step</param>
/// <param name="value"></param>
/// <returns></returns>
IStepBuilder<TData, TStepBody> Input<TInput>(Expression<Func<TStepBody, TInput>> stepProperty, Expression<Func<TData, IStepExecutionContext, TInput>> value);
Expand All @@ -88,21 +88,30 @@ public interface IStepBuilder<TData, TStepBody>
IStepBuilder<TData, TStepBody> Output<TOutput>(Expression<Func<TData, TOutput>> dataProperty, Expression<Func<TStepBody, TOutput>> value);

/// <summary>
/// Put the workflow to sleep until to specified event is published
/// Wait here until to specified event is published
/// </summary>
/// <param name="eventName">The name used to identify the kind of event to wait for</param>
/// <param name="eventKey">A specific key value within the context of the event to wait for</param>
/// <param name="effectiveDate">Listen for events as of this effective date</param>
/// <returns></returns>
IStepBuilder<TData, WaitFor> WaitFor(string eventName, Expression<Func<TData, string>> eventKey, Expression<Func<TData, DateTime>> effectiveDate = null);

/// <summary>
/// Wait here until to specified event is published
/// </summary>
/// <param name="eventName"></param>
/// <param name="eventKey"></param>
/// <param name="effectiveDate"></param>
/// <param name="eventName">The name used to identify the kind of event to wait for</param>
/// <param name="eventKey">A specific key value within the context of the event to wait for</param>
/// <param name="effectiveDate">Listen for events as of this effective date</param>
/// <returns></returns>
IStepBuilder<TData, SubscriptionStepBody> WaitFor(string eventName, Expression<Func<TData, string>> eventKey, Expression<Func<TData, DateTime>> effectiveDate = null);
IStepBuilder<TData, WaitFor> WaitFor(string eventName, Expression<Func<TData, IStepExecutionContext, string>> eventKey, Expression<Func<TData, DateTime>> effectiveDate = null);

IStepBuilder<TData, TStep> End<TStep>(string name) where TStep : IStepBody;

/// <summary>
/// Configure the behavior when this step throws an unhandled exception
/// </summary>
/// <param name="behavior"></param>
/// <param name="retryInterval"></param>
/// <param name="behavior">What action to take when this step throws an unhandled exception</param>
/// <param name="retryInterval">If the behavior is retry, how often</param>
/// <returns></returns>
IStepBuilder<TData, TStepBody> OnError(WorkflowErrorHandling behavior, TimeSpan? retryInterval = null);

Expand All @@ -122,21 +131,21 @@ public interface IStepBuilder<TData, TStepBody>
/// <summary>
/// Execute a block of steps, once for each item in a collection in a parallel foreach
/// </summary>
/// <param name="collection"></param>
/// <param name="collection">Resolves a collection for iterate over</param>
/// <returns></returns>
IContainerStepBuilder<TData, Foreach, Foreach> ForEach(Expression<Func<TData, IEnumerable>> collection);

/// <summary>
/// Repeat a block of steps until a condition becomes true
/// </summary>
/// <param name="condition"></param>
/// <param name="condition">Resolves a condition to break out of the while loop</param>
/// <returns></returns>
IContainerStepBuilder<TData, While, While> While(Expression<Func<TData, bool>> condition);

/// <summary>
/// Execute a block of steps if a condition is true
/// </summary>
/// <param name="condition"></param>
/// <param name="condition">Resolves a condition to evaluate</param>
/// <returns></returns>
IContainerStepBuilder<TData, If, If> If(Expression<Func<TData, bool>> condition);

Expand All @@ -156,8 +165,16 @@ public interface IStepBuilder<TData, TStepBody>
/// <summary>
/// Schedule a block of steps to execute in parallel sometime in the future
/// </summary>
/// <param name="time"></param>
/// <param name="time">The time span to wait before executing the block</param>
/// <returns></returns>
IContainerStepBuilder<TData, Schedule, TStepBody> Schedule(Expression<Func<TData, TimeSpan>> time);

/// <summary>
/// Schedule a block of steps to execute in parallel sometime in the future at a recurring interval
/// </summary>
/// <param name="interval">The time span to wait between recurring executions</param>
/// <param name="until">Resolves a condition to stop the recurring task</param>
/// <returns></returns>
IContainerStepBuilder<TData, Recur, TStepBody> Recur(Expression<Func<TData, TimeSpan>> interval, Expression<Func<TData, bool>> until);
}
}
24 changes: 24 additions & 0 deletions src/WorkflowCore/Interface/IWorkflowHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ namespace WorkflowCore.Interface
{
public interface IWorkflowHost
{
/// <summary>
/// Start the workflow host, this enable execution of workflows
/// </summary>
void Start();

/// <summary>
/// Stop the workflow host
/// </summary>
void Stop();
Task<string> StartWorkflow(string workflowId, object data = null);
Task<string> StartWorkflow(string workflowId, int? version, object data = null);
Expand All @@ -20,8 +27,25 @@ public interface IWorkflowHost
void RegisterWorkflow<TWorkflow>() where TWorkflow : IWorkflow, new();
void RegisterWorkflow<TWorkflow, TData>() where TWorkflow : IWorkflow<TData>, new() where TData : new();

/// <summary>
/// Suspend the execution of a given workflow until .ResumeWorkflow is called
/// </summary>
/// <param name="workflowId"></param>
/// <returns></returns>
Task<bool> SuspendWorkflow(string workflowId);

/// <summary>
/// Resume a previously suspended workflow
/// </summary>
/// <param name="workflowId"></param>
/// <returns></returns>
Task<bool> ResumeWorkflow(string workflowId);

/// <summary>
/// Permanently terminate the exeuction of a given workflow
/// </summary>
/// <param name="workflowId"></param>
/// <returns></returns>
Task<bool> TerminateWorkflow(string workflowId);

event StepErrorEventHandler OnStepError;
Expand Down
16 changes: 16 additions & 0 deletions src/WorkflowCore/Models/ExecutionResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ public class ExecutionResult

public object PersistenceData { get; set; }

public string EventName { get; set; }

public string EventKey { get; set; }

public DateTime EventAsOf { get; set; }

public List<object> BranchValues { get; set; } = new List<object>();

public ExecutionResult()
Expand Down Expand Up @@ -74,5 +80,15 @@ public static ExecutionResult Sleep(TimeSpan duration, object persistenceData)
};
}

public static ExecutionResult WaitForEvent(string eventName, string eventKey, DateTime effectiveDate)
{
return new ExecutionResult()
{
Proceed = false,
EventName = eventName,
EventKey = eventKey,
EventAsOf = effectiveDate.ToUniversalTime()
};
}
}
}
19 changes: 13 additions & 6 deletions src/WorkflowCore/Models/WorkflowStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ public virtual void AfterExecute(WorkflowExecutorResult executorResult, IStepExe
{
}

/// <summary>
/// Called after every workflow execution round,
/// every exectuon pointer with no end time, even if this step was not executed in this round
/// </summary>
/// <param name="executorResult"></param>
/// <param name="defintion"></param>
/// <param name="workflow"></param>
/// <param name="executionPointer"></param>
public virtual void AfterWorkflowIteration(WorkflowExecutorResult executorResult, WorkflowDefinition defintion, WorkflowInstance workflow, ExecutionPointer executionPointer)
{
}

public virtual IStepBody ConstructBody(IServiceProvider serviceProvider)
{
IStepBody body = (serviceProvider.GetService(BodyType) as IStepBody);
Expand All @@ -51,7 +63,6 @@ public virtual IStepBody ConstructBody(IServiceProvider serviceProvider)
if (stepCtor != null)
body = (stepCtor.Invoke(null) as IStepBody);
}

return body;
}

Expand All @@ -62,11 +73,7 @@ public enum ExecutionPipelineDirective { Next = 0, Defer = 1, EndWorkflow = 2 }
public class WorkflowStep<TStepBody> : WorkflowStep
where TStepBody : IStepBody
{
public override Type BodyType
{
get { return typeof(TStepBody); }
}

public override Type BodyType => typeof(TStepBody);
}


Expand Down
31 changes: 31 additions & 0 deletions src/WorkflowCore/Primitives/CancellableStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace WorkflowCore.Primitives
{
public class CancellableStep<TStepBody, TData> : WorkflowStep<TStepBody>
where TStepBody : IStepBody
{
private readonly Expression<Func<TData, bool>> _cancelCondition;

public CancellableStep(Expression<Func<TData, bool>> cancelCondition)
{
_cancelCondition = cancelCondition;
}

public override void AfterWorkflowIteration(WorkflowExecutorResult executorResult, WorkflowDefinition defintion, WorkflowInstance workflow, ExecutionPointer executionPointer)
{
base.AfterWorkflowIteration(executorResult, defintion, workflow, executionPointer);
var func = _cancelCondition.Compile();
if (func((TData) workflow.Data))
{
executionPointer.EndTime = DateTime.Now.ToUniversalTime();
executionPointer.Active = false;
}
}
}
}
29 changes: 29 additions & 0 deletions src/WorkflowCore/Primitives/Recur.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace WorkflowCore.Primitives
{
public class Recur : ContainerStepBody
{
public TimeSpan Interval { get; set; }

public bool StopCondition { get; set; }

public override ExecutionResult Run(IStepExecutionContext context)
{
if (StopCondition)
return ExecutionResult.Next();

return new ExecutionResult()
{
Proceed = false,
BranchValues = new List<object>() { null },
SleepFor = Interval
};
}
}
}
Loading

0 comments on commit be4091f

Please sign in to comment.