Skip to content

Commit b09e07e

Browse files
authored
Workflow Core v1.7 (danielgerlag#237)
1 parent 0ac2eed commit b09e07e

File tree

83 files changed

+999
-298
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+999
-298
lines changed

ReleaseNotes/1.7.0.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Workflow Core 1.7.0
2+
3+
* Various performance optimizations, any users of the EntityFramework persistence providers will have to update their persistence libraries to the latest version as well.
4+
* Added `CancelCondition` to fluent builder API.
5+
6+
```
7+
.CancelCondition(data => <<expression>>, <<Continue after cancellation>>)
8+
9+
```
10+
11+
This allows you to specify a condition under which any active step can be prematurely cancelled.
12+
For example, suppose you create a future scheduled task, but you want to cancel the future execution of this task if some condition becomes true.
13+
14+
15+
```c#
16+
builder
17+
.StartWith(context => Console.WriteLine("Hello"))
18+
.Schedule(data => TimeSpan.FromSeconds(5)).Do(schedule => schedule
19+
.StartWith<DoSomething>()
20+
.Then<DoSomethingFurther>()
21+
)
22+
.CancelCondition(data => !data.SheduledTaskRequired)
23+
.Then(context => Console.WriteLine("Doing normal tasks"));
24+
```
25+
26+
You could also use this implement a parallel flow where once a single path completes, all the other paths are cancelled.
27+
28+
```c#
29+
.Parallel()
30+
.Do(then => then
31+
.StartWith<DoSomething>()
32+
.WaitFor("Approval", (data, context) => context.Workflow.IdNow)
33+
)
34+
.Do(then => then
35+
.StartWith<DoSomething>()
36+
.Delay(data => TimeSpan.FromDays(3))
37+
.Then<EscalateIssue>()
38+
)
39+
.Join()
40+
.CancelCondition(data => data.IsApproved, true)
41+
.Then<MoveAlong>();
42+
```
43+
44+
* Deprecated `WorkflowCore.LockProviders.RedLock` in favour of `WorkflowCore.Providers.Redis`
45+
* Create a new `WorkflowCore.Providers.Redis` library that includes providers for distributed locking, queues and event hubs.
46+
* Provides Queueing support backed by Redis.
47+
* Provides Distributed locking support backed by Redis.
48+
* Provides event hub support backed by Redis.
49+
50+
This makes it possible to have a cluster of nodes processing your workflows.
51+
52+
## Installing
53+
54+
Install the NuGet package "WorkflowCore.Providers.Redis"
55+
56+
Using Nuget package console
57+
```
58+
PM> Install-Package WorkflowCore.Providers.Redis
59+
```
60+
Using .NET CLI
61+
```
62+
dotnet add package WorkflowCore.Providers.Redis
63+
```
64+
65+
66+
## Usage
67+
68+
Use the `IServiceCollection` extension methods when building your service provider
69+
* .UseRedisQueues
70+
* .UseRedisLocking
71+
* .UseRedisEventHub
72+
73+
```C#
74+
services.AddWorkflow(cfg =>
75+
{
76+
cfg.UseRedisLocking("localhost:6379");
77+
cfg.UseRedisQueues("localhost:6379", "my-app");
78+
cfg.UseRedisEventHub("localhost:6379", "my-channel")
79+
});
80+
```

WorkflowCore.sln

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReleaseNotes", "ReleaseNote
9494
ReleaseNotes\1.6.6.md = ReleaseNotes\1.6.6.md
9595
ReleaseNotes\1.6.8.md = ReleaseNotes\1.6.8.md
9696
ReleaseNotes\1.6.9.md = ReleaseNotes\1.6.9.md
97+
ReleaseNotes\1.7.0.md = ReleaseNotes\1.7.0.md
9798
EndProjectSection
9899
EndProject
99100
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample14", "src\samples\WorkflowCore.Sample14\WorkflowCore.Sample14.csproj", "{6BC66637-B42A-4334-ADFB-DBEC9F29D293}"
@@ -120,7 +121,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.My
120121
EndProject
121122
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.MySQL", "test\WorkflowCore.Tests.MySQL\WorkflowCore.Tests.MySQL.csproj", "{DF7F7ECA-1771-40C9-9CD0-AFEFC44E60DE}"
122123
EndProject
123-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.DynamoDB", "WorkflowCore.Tests.DynamoDB\WorkflowCore.Tests.DynamoDB.csproj", "{0FF3F27E-E909-4ABA-BF82-39D1BA133EA7}"
124+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.DynamoDB", "test\WorkflowCore.Tests.DynamoDB\WorkflowCore.Tests.DynamoDB.csproj", "{3ECEC028-7E2C-4983-B928-26C073B51BB7}"
125+
EndProject
126+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Providers.Redis", "src\providers\WorkflowCore.Providers.Redis\WorkflowCore.Providers.Redis.csproj", "{435C6263-C6F8-4E93-B417-D861E9C22E18}"
124127
EndProject
125128
Global
126129
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -304,10 +307,14 @@ Global
304307
{DF7F7ECA-1771-40C9-9CD0-AFEFC44E60DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
305308
{DF7F7ECA-1771-40C9-9CD0-AFEFC44E60DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
306309
{DF7F7ECA-1771-40C9-9CD0-AFEFC44E60DE}.Release|Any CPU.Build.0 = Release|Any CPU
307-
{0FF3F27E-E909-4ABA-BF82-39D1BA133EA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
308-
{0FF3F27E-E909-4ABA-BF82-39D1BA133EA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
309-
{0FF3F27E-E909-4ABA-BF82-39D1BA133EA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
310-
{0FF3F27E-E909-4ABA-BF82-39D1BA133EA7}.Release|Any CPU.Build.0 = Release|Any CPU
310+
{3ECEC028-7E2C-4983-B928-26C073B51BB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
311+
{3ECEC028-7E2C-4983-B928-26C073B51BB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
312+
{3ECEC028-7E2C-4983-B928-26C073B51BB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
313+
{3ECEC028-7E2C-4983-B928-26C073B51BB7}.Release|Any CPU.Build.0 = Release|Any CPU
314+
{435C6263-C6F8-4E93-B417-D861E9C22E18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
315+
{435C6263-C6F8-4E93-B417-D861E9C22E18}.Debug|Any CPU.Build.0 = Debug|Any CPU
316+
{435C6263-C6F8-4E93-B417-D861E9C22E18}.Release|Any CPU.ActiveCfg = Release|Any CPU
317+
{435C6263-C6F8-4E93-B417-D861E9C22E18}.Release|Any CPU.Build.0 = Release|Any CPU
311318
EndGlobalSection
312319
GlobalSection(SolutionProperties) = preSolution
313320
HideSolutionNode = FALSE
@@ -360,7 +367,8 @@ Global
360367
{5E82A137-0954-46A1-8C46-13C00F0E4842} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
361368
{453E260D-DBDC-4DDC-BC9C-CA500CED7897} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
362369
{DF7F7ECA-1771-40C9-9CD0-AFEFC44E60DE} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
363-
{0FF3F27E-E909-4ABA-BF82-39D1BA133EA7} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
370+
{3ECEC028-7E2C-4983-B928-26C073B51BB7} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
371+
{435C6263-C6F8-4E93-B417-D861E9C22E18} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
364372
EndGlobalSection
365373
GlobalSection(ExtensibilityGlobals) = postSolution
366374
SolutionGuid = {DC0FA8D3-6449-4FDA-BB46-ECF58FAD23B4}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using WorkflowCore.Models;
5+
6+
namespace WorkflowCore.Interface
7+
{
8+
public interface ICancellationProcessor
9+
{
10+
void ProcessCancellations(WorkflowInstance workflow, WorkflowDefinition workflowDef, WorkflowExecutorResult executionResult);
11+
}
12+
}

src/WorkflowCore/Interface/ILifeCycleEventHub.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ public interface ILifeCycleEventHub
1010
{
1111
Task PublishNotification(LifeCycleEvent evt);
1212
void Subscribe(Action<LifeCycleEvent> action);
13+
Task Start();
14+
Task Stop();
1315
}
1416
}

src/WorkflowCore/Interface/IStepBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ public interface IStepBuilder<TData, TStepBody>
213213
/// <param name="builder"></param>
214214
/// <returns></returns>
215215
IStepBuilder<TData, TStepBody> CompensateWithSequence(Action<IWorkflowBuilder<TData>> builder);
216+
217+
/// <summary>
218+
/// Prematurely cancel the execution of this step on a condition
219+
/// </summary>
220+
/// <param name="cancelCondition"></param>
221+
/// <returns></returns>
222+
IStepBuilder<TData, TStepBody> CancelCondition(Expression<Func<TData, bool>> cancelCondition, bool proceedAfterCancel = false);
216223

217224
}
218225
}

src/WorkflowCore/Models/ExecutionPointer.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace WorkflowCore.Models
66
{
77
public class ExecutionPointer
88
{
9+
private IReadOnlyCollection<string> _scope = new List<string>();
10+
911
public string Id { get; set; }
1012

1113
public int StepId { get; set; }
@@ -26,8 +28,8 @@ public class ExecutionPointer
2628

2729
public bool EventPublished { get; set; }
2830

29-
public object EventData { get; set; }
30-
31+
public object EventData { get; set; }
32+
3133
public Dictionary<string, object> ExtensionAttributes { get; set; } = new Dictionary<string, object>();
3234

3335
public string StepName { get; set; }
@@ -43,9 +45,12 @@ public class ExecutionPointer
4345
public object Outcome { get; set; }
4446

4547
public PointerStatus Status { get; set; } = PointerStatus.Legacy;
46-
47-
public Stack<string> Scope { get; set; } = new Stack<string>();
48-
48+
49+
public IReadOnlyCollection<string> Scope
50+
{
51+
get => _scope;
52+
set => _scope = new List<string>(value);
53+
}
4954
}
5055

5156
public enum PointerStatus
@@ -57,6 +62,7 @@ public enum PointerStatus
5762
Sleeping = 4,
5863
WaitingForEvent = 5,
5964
Failed = 6,
60-
Compensated = 7
65+
Compensated = 7,
66+
Cancelled = 8
6167
}
6268
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
7+
namespace WorkflowCore.Models
8+
{
9+
public class ExecutionPointerCollection : ICollection<ExecutionPointer>
10+
{
11+
private readonly Dictionary<string, ExecutionPointer> _dictionary = new Dictionary<string, ExecutionPointer>();
12+
private readonly Dictionary<string, ICollection<ExecutionPointer>> _scopeMap = new Dictionary<string, ICollection<ExecutionPointer>>();
13+
14+
public ExecutionPointerCollection()
15+
{
16+
}
17+
18+
public ExecutionPointerCollection(int capacity)
19+
{
20+
_dictionary = new Dictionary<string, ExecutionPointer>(capacity);
21+
}
22+
23+
public ExecutionPointerCollection(ICollection<ExecutionPointer> pointers)
24+
{
25+
foreach (var ptr in pointers)
26+
{
27+
Add(ptr);
28+
}
29+
}
30+
31+
public IEnumerator<ExecutionPointer> GetEnumerator()
32+
{
33+
return _dictionary.Values.GetEnumerator();
34+
}
35+
36+
IEnumerator IEnumerable.GetEnumerator()
37+
{
38+
return GetEnumerator();
39+
}
40+
41+
public ExecutionPointer FindById(string id)
42+
{
43+
if (!_dictionary.ContainsKey(id))
44+
return null;
45+
46+
return _dictionary[id];
47+
}
48+
49+
public ICollection<ExecutionPointer> FindByScope(string stackFrame)
50+
{
51+
if (!_scopeMap.ContainsKey(stackFrame))
52+
return new List<ExecutionPointer>();
53+
54+
return _scopeMap[stackFrame];
55+
}
56+
57+
public void Add(ExecutionPointer item)
58+
{
59+
_dictionary.Add(item.Id, item);
60+
61+
foreach (var stackFrame in item.Scope)
62+
{
63+
if (!_scopeMap.ContainsKey(stackFrame))
64+
_scopeMap.Add(stackFrame, new List<ExecutionPointer>());
65+
_scopeMap[stackFrame].Add(item);
66+
}
67+
}
68+
69+
public void Clear()
70+
{
71+
_dictionary.Clear();
72+
_scopeMap.Clear();
73+
}
74+
75+
public bool Contains(ExecutionPointer item)
76+
{
77+
return _dictionary.ContainsValue(item);
78+
}
79+
80+
public void CopyTo(ExecutionPointer[] array, int arrayIndex)
81+
{
82+
_dictionary.Values.CopyTo(array, arrayIndex);
83+
}
84+
85+
public bool Remove(ExecutionPointer item)
86+
{
87+
foreach (var stackFrame in item.Scope)
88+
{
89+
_scopeMap[stackFrame].Remove(item);
90+
}
91+
92+
return _dictionary.Remove(item.Id);
93+
}
94+
95+
public ExecutionPointer Find(Predicate<ExecutionPointer> match)
96+
{
97+
return _dictionary.Values.FirstOrDefault(x => match(x));
98+
}
99+
100+
public int Count => _dictionary.Count;
101+
public bool IsReadOnly => false;
102+
}
103+
}

src/WorkflowCore/Models/WorkflowInstance.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34

45
namespace WorkflowCore.Models
56
{
@@ -15,7 +16,7 @@ public class WorkflowInstance
1516

1617
public string Reference { get; set; }
1718

18-
public List<ExecutionPointer> ExecutionPointers { get; set; } = new List<ExecutionPointer>();
19+
public ExecutionPointerCollection ExecutionPointers { get; set; } = new ExecutionPointerCollection();
1920

2021
public long? NextExecution { get; set; }
2122

@@ -25,8 +26,14 @@ public class WorkflowInstance
2526

2627
public DateTime CreateTime { get; set; }
2728

28-
public DateTime? CompleteTime { get; set; }
29+
public DateTime? CompleteTime { get; set; }
2930

31+
public bool IsBranchComplete(string parentId)
32+
{
33+
return ExecutionPointers
34+
.FindByScope(parentId)
35+
.All(x => x.EndTime != null);
36+
}
3037
}
3138

3239
public enum WorkflowStatus

src/WorkflowCore/Models/WorkflowStep.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq.Expressions;
34
using System.Reflection;
45
using WorkflowCore.Interface;
56

@@ -33,6 +34,10 @@ public abstract class WorkflowStep
3334

3435
public virtual bool RevertChildrenAfterCompensation => false;
3536

37+
public virtual LambdaExpression CancelCondition { get; set; }
38+
39+
public bool ProceedOnCancel { get; set; } = false;
40+
3641
public virtual ExecutionPipelineDirective InitForExecution(WorkflowExecutorResult executorResult, WorkflowDefinition defintion, WorkflowInstance workflow, ExecutionPointer executionPointer)
3742
{
3843
return ExecutionPipelineDirective.Next;
@@ -61,6 +66,7 @@ public virtual void PrimeForRetry(ExecutionPointer pointer)
6166
/// <param name="executionPointer"></param>
6267
public virtual void AfterWorkflowIteration(WorkflowExecutorResult executorResult, WorkflowDefinition defintion, WorkflowInstance workflow, ExecutionPointer executionPointer)
6368
{
69+
6470
}
6571

6672
public virtual IStepBody ConstructBody(IServiceProvider serviceProvider)

0 commit comments

Comments
 (0)