Skip to content

Commit

Permalink
Target netstandard2, Yaml support, object graph support (#328)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgerlag authored Jun 30, 2019
1 parent d161f84 commit c182dce
Show file tree
Hide file tree
Showing 40 changed files with 475 additions and 311 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public class MyWorkflow : IWorkflow
}
```

## JSON Workflow Definitions
## JSON / YAML Workflow Definitions

Define your workflows in JSON
Define your workflows in JSON or YAML

```json
{
Expand All @@ -47,6 +47,17 @@ Define your workflows in JSON
}
```

```yaml
Id: HelloWorld
Version: 1
Steps:
- Id: Hello
StepType: MyApp.HelloWorld, MyApp
NextStepId: Bye
- Id: Bye
StepType: MyApp.GoodbyeWorld, MyApp
```
### Sample use cases
* New user workflow
Expand Down
72 changes: 72 additions & 0 deletions ReleaseNotes/2.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Workflow Core 2.0.0

### Upgrade notes
Existing JSON definitions will be loaded as follows
```c#
using WorkflowCore.Services.DefinitionStorage;
...
DefinitionLoader.LoadDefinition(json, Deserializers.Json);
```


* Targets .NET Standard 2.0

The core library now targets .NET Standard 2.0, in order to leverage newer features.

* Support for YAML definitions

Added support for YAML workflow definitions, which can be loaded as follows
```c#
using WorkflowCore.Services.DefinitionStorage;
...
DefinitionLoader.LoadDefinition(json, Deserializers.Yaml);
```

Existing JSON definitions will be loaded as follows
```c#
using WorkflowCore.Services.DefinitionStorage;
...
DefinitionLoader.LoadDefinition(json, Deserializers.Json);
```

* Object graphs and inline expressions on input properties

You can now pass object graphs to step inputs as opposed to just scalar values
```
"inputs":
{
"Body": {
"Value1": 1,
"Value2": 2
},
"Headers": {
"Content-Type": "application/json"
}
},
```
If you want to evaluate an expression for a given property of your object, simply prepend and `@` and pass an expression string
```
"inputs":
{
"Body": {
"@Value1": "data.MyValue * 2",
"Value2": 5
},
"Headers": {
"Content-Type": "application/json"
}
},
```

* Support for enum values on input properties

If your step has an enum property, you can now just pass the string representation of the enum value and it will be automatically converted.

* Environment variables available in input expressions

You can now access environment variables from within input expressions.
usage:
```
environment["VARIABLE_NAME"]
```

15 changes: 1 addition & 14 deletions WorkflowCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.Po
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.Sqlite", "src\providers\WorkflowCore.Persistence.Sqlite\WorkflowCore.Persistence.Sqlite.csproj", "{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.LockProviders.Redlock", "src\providers\WorkflowCore.LockProviders.Redlock\WorkflowCore.LockProviders.Redlock.csproj", "{05250D58-A59E-4212-8D55-E7BC0396E9F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.QueueProviders.RabbitMQ", "src\providers\WorkflowCore.QueueProviders.RabbitMQ\WorkflowCore.QueueProviders.RabbitMQ.csproj", "{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample06", "src\samples\WorkflowCore.Sample06\WorkflowCore.Sample06.csproj", "{8FEAFD74-C304-4F75-BA38-4686BE55C891}"
Expand Down Expand Up @@ -100,6 +98,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReleaseNotes", "ReleaseNote
ReleaseNotes\1.9.0.md = ReleaseNotes\1.9.0.md
ReleaseNotes\1.9.2.md = ReleaseNotes\1.9.2.md
ReleaseNotes\1.9.3.md = ReleaseNotes\1.9.3.md
ReleaseNotes\2.0.0.md = ReleaseNotes\2.0.0.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample14", "src\samples\WorkflowCore.Sample14\WorkflowCore.Sample14.csproj", "{6BC66637-B42A-4334-ADFB-DBEC9F29D293}"
Expand All @@ -114,8 +113,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample15", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample16", "src\samples\WorkflowCore.Sample16\WorkflowCore.Sample16.csproj", "{0C9617A9-C8B7-45F6-A54A-261A23AC881B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScratchPad", "test\ScratchPad\ScratchPad.csproj", "{6396453F-4D0E-4CD4-BC89-87E8970F2A80}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample17", "src\samples\WorkflowCore.Sample17\WorkflowCore.Sample17.csproj", "{42F475BC-95F4-42E1-8CCD-7B9C27487E33}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.QueueProviders.SqlServer", "src\providers\WorkflowCore.QueueProviders.SqlServer\WorkflowCore.QueueProviders.SqlServer.csproj", "{7EDD9353-F5C2-414C-AE51-4B0F1C5E105A}"
Expand Down Expand Up @@ -186,10 +183,6 @@ Global
{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}.Release|Any CPU.Build.0 = Release|Any CPU
{05250D58-A59E-4212-8D55-E7BC0396E9F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05250D58-A59E-4212-8D55-E7BC0396E9F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05250D58-A59E-4212-8D55-E7BC0396E9F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05250D58-A59E-4212-8D55-E7BC0396E9F5}.Release|Any CPU.Build.0 = Release|Any CPU
{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -294,10 +287,6 @@ Global
{0C9617A9-C8B7-45F6-A54A-261A23AC881B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C9617A9-C8B7-45F6-A54A-261A23AC881B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C9617A9-C8B7-45F6-A54A-261A23AC881B}.Release|Any CPU.Build.0 = Release|Any CPU
{6396453F-4D0E-4CD4-BC89-87E8970F2A80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6396453F-4D0E-4CD4-BC89-87E8970F2A80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6396453F-4D0E-4CD4-BC89-87E8970F2A80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6396453F-4D0E-4CD4-BC89-87E8970F2A80}.Release|Any CPU.Build.0 = Release|Any CPU
{42F475BC-95F4-42E1-8CCD-7B9C27487E33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42F475BC-95F4-42E1-8CCD-7B9C27487E33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42F475BC-95F4-42E1-8CCD-7B9C27487E33}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -357,7 +346,6 @@ Global
{1DE96D4F-F2CA-4740-8764-BADD1000040A} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{9274B938-3996-4FBA-AE2F-0C82009B1116} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{86BC1E05-E9CE-4E53-B324-885A2FDBCE74} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{05250D58-A59E-4212-8D55-E7BC0396E9F5} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{8FEAFD74-C304-4F75-BA38-4686BE55C891} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
{37B598A8-B054-4ABA-884D-96AEF2511600} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
Expand All @@ -384,7 +372,6 @@ Global
{EC497168-5347-4E70-9D9E-9C2F826C1CDF} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
{9B7811AC-68D6-4D19-B1E9-65423393ED83} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
{0C9617A9-C8B7-45F6-A54A-261A23AC881B} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
{6396453F-4D0E-4CD4-BC89-87E8970F2A80} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
{42F475BC-95F4-42E1-8CCD-7B9C27487E33} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
{7EDD9353-F5C2-414C-AE51-4B0F1C5E105A} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{5E82A137-0954-46A1-8C46-13C00F0E4842} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
Expand Down
6 changes: 4 additions & 2 deletions src/WorkflowCore/Interface/IDefinitionLoader.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using WorkflowCore.Models;
using System;
using WorkflowCore.Models;
using WorkflowCore.Models.DefinitionStorage.v1;

namespace WorkflowCore.Interface
{
public interface IDefinitionLoader
{
WorkflowDefinition LoadDefinition(string json);
WorkflowDefinition LoadDefinition(string source, Func<string, DefinitionSourceV1> deserializer);
}
}
3 changes: 2 additions & 1 deletion src/WorkflowCore/Models/DefinitionStorage/v1/StepSourceV1.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text;

namespace WorkflowCore.Models.DefinitionStorage.v1
Expand All @@ -26,7 +27,7 @@ public class StepSourceV1

public string NextStepId { get; set; }

public Dictionary<string, string> Inputs { get; set; } = new Dictionary<string, string>();
public ExpandoObject Inputs { get; set; } = new ExpandoObject();

public Dictionary<string, string> Outputs { get; set; } = new Dictionary<string, string>();

Expand Down
76 changes: 68 additions & 8 deletions src/WorkflowCore/Services/DefinitionStorage/DefinitionLoader.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Newtonsoft.Json.Linq;
using WorkflowCore.Interface;
using WorkflowCore.Models;
using WorkflowCore.Primitives;
Expand All @@ -24,10 +26,10 @@ public DefinitionLoader(IWorkflowRegistry registry)
_registry = registry;
}

public WorkflowDefinition LoadDefinition(string json)
public WorkflowDefinition LoadDefinition(string source, Func<string, DefinitionSourceV1> deserializer)
{
var source = JsonConvert.DeserializeObject<DefinitionSourceV1>(json);
var def = Convert(source);
var sourceObj = deserializer(source);
var def = Convert(sourceObj);
_registry.RegisterWorkflow(def);
return def;
}
Expand Down Expand Up @@ -171,13 +173,24 @@ private void AttachInputs(StepSourceV1 source, Type dataType, Type stepType, Wor
{
var dataParameter = Expression.Parameter(dataType, "data");
var contextParameter = Expression.Parameter(typeof(IStepExecutionContext), "context");
var sourceExpr = DynamicExpressionParser.ParseLambda(new [] { dataParameter, contextParameter }, typeof(object), input.Value);
var environmentVarsParameter = Expression.Parameter(typeof(IDictionary), "environment");
var stepProperty = stepType.GetProperty(input.Key);

var stepParameter = Expression.Parameter(stepType, "step");
var targetProperty = Expression.Property(stepParameter, input.Key);
var targetExpr = Expression.Lambda(targetProperty, stepParameter);
if (input.Value is string)
{
var acn = BuildScalarInputAction(input, dataParameter, contextParameter, environmentVarsParameter, stepProperty);
step.Inputs.Add(new ActionParameter<IStepBody, object>(acn));
continue;
}

step.Inputs.Add(new MemberMapParameter(sourceExpr, targetExpr));
if ((input.Value is IDictionary<string, object>) || (input.Value is IDictionary<object, object>))
{
var acn = BuildObjectInputAction(input, dataParameter, contextParameter, environmentVarsParameter, stepProperty);
step.Inputs.Add(new ActionParameter<IStepBody, object>(acn));
continue;
}

throw new ArgumentException($"Unknown type for input {input.Key} on {source.Id}");
}
}

Expand Down Expand Up @@ -221,5 +234,52 @@ private Type FindType(string name)
return Type.GetType(name, true, true);
}

private static Action<IStepBody, object, IStepExecutionContext> BuildScalarInputAction(KeyValuePair<string, object> input, ParameterExpression dataParameter, ParameterExpression contextParameter, ParameterExpression environmentVarsParameter, PropertyInfo stepProperty)
{
var expr = System.Convert.ToString(input.Value);
var sourceExpr = DynamicExpressionParser.ParseLambda(new[] { dataParameter, contextParameter, environmentVarsParameter }, typeof(object), expr);

void acn(IStepBody pStep, object pData, IStepExecutionContext pContext)
{
object resolvedValue = sourceExpr.Compile().DynamicInvoke(pData, pContext, Environment.GetEnvironmentVariables());
if (stepProperty.PropertyType.IsEnum)
stepProperty.SetValue(pStep, Enum.Parse(stepProperty.PropertyType, (string)resolvedValue, true));
else
stepProperty.SetValue(pStep, System.Convert.ChangeType(resolvedValue, stepProperty.PropertyType));
}
return acn;
}

private static Action<IStepBody, object, IStepExecutionContext> BuildObjectInputAction(KeyValuePair<string, object> input, ParameterExpression dataParameter, ParameterExpression contextParameter, ParameterExpression environmentVarsParameter, PropertyInfo stepProperty)
{
void acn(IStepBody pStep, object pData, IStepExecutionContext pContext)
{
var stack = new Stack<JObject>();
var destObj = JObject.FromObject(input.Value);
stack.Push(destObj);

while (stack.Count > 0)
{
var subobj = stack.Pop();
foreach (var prop in subobj.Properties().ToList())
{
if (prop.Name.StartsWith("@"))
{
var sourceExpr = DynamicExpressionParser.ParseLambda(new[] { dataParameter, contextParameter, environmentVarsParameter }, typeof(object), prop.Value.ToString());
object resolvedValue = sourceExpr.Compile().DynamicInvoke(pData, pContext, Environment.GetEnvironmentVariables());
subobj.Remove(prop.Name);
subobj.Add(prop.Name.TrimStart('@'), JToken.FromObject(resolvedValue));
}
}

foreach (var child in subobj.Children<JObject>())
stack.Push(child);
}

stepProperty.SetValue(pStep, destObj);
}
return acn;
}

}
}
16 changes: 16 additions & 0 deletions src/WorkflowCore/Services/DefinitionStorage/Deserializers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using Newtonsoft.Json;
using SharpYaml.Serialization;
using WorkflowCore.Models.DefinitionStorage.v1;

namespace WorkflowCore.Services.DefinitionStorage
{
public static class Deserializers
{
private static Serializer yamlSerializer = new Serializer();

public static Func<string, DefinitionSourceV1> Json = (source) => JsonConvert.DeserializeObject<DefinitionSourceV1>(source);

public static Func<string, DefinitionSourceV1> Yaml = (source) => yamlSerializer.DeserializeInto(source, new DefinitionSourceV1());
}
}
23 changes: 8 additions & 15 deletions src/WorkflowCore/WorkflowCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyTitle>Workflow Core</AssemblyTitle>
<Authors>Daniel Gerlag</Authors>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>WorkflowCore</AssemblyName>
<PackageId>WorkflowCore</PackageId>
<PackageTags>workflow;.NET;Core;state machine</PackageTags>
Expand All @@ -15,33 +15,26 @@
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Description>Workflow Core is a light weight workflow engine targeting .NET Standard.</Description>
<Version>1.9.4</Version>
<AssemblyVersion>1.9.4.0</AssemblyVersion>
<FileVersion>1.9.4.0</FileVersion>
<Version>2.0.0</Version>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
<FileVersion>2.0.0.0</FileVersion>
<PackageReleaseNotes></PackageReleaseNotes>
<PackageIconUrl>https://github.com/danielgerlag/workflow-core/raw/master/src/logo.png</PackageIconUrl>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="SharpYaml" Version="1.6.5" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.7.12" />
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.3.0" />
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
<PackageReference Include="System.Linq.Queryable" Version="4.3.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>

</Project>
Loading

0 comments on commit c182dce

Please sign in to comment.