Skip to content

ActivityName as expression. It solves #542 issue. (Activity Compound Key) #838

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 27, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions WorkflowCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample19", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Persistence.RavenDB", "src\providers\WorkflowCore.Persistence.RavenDB\WorkflowCore.Persistence.RavenDB.csproj", "{AF205715-C8B7-42EF-BF14-AFC9E7F27242}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample20", "src\samples\WorkflowCore.Sample20\WorkflowCore.Sample20.csproj", "{68D1B955-1049-477B-A894-13FCB96C45DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -374,6 +376,10 @@ Global
{AF205715-C8B7-42EF-BF14-AFC9E7F27242}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF205715-C8B7-42EF-BF14-AFC9E7F27242}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF205715-C8B7-42EF-BF14-AFC9E7F27242}.Release|Any CPU.Build.0 = Release|Any CPU
{68D1B955-1049-477B-A894-13FCB96C45DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68D1B955-1049-477B-A894-13FCB96C45DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68D1B955-1049-477B-A894-13FCB96C45DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68D1B955-1049-477B-A894-13FCB96C45DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -436,6 +442,7 @@ Global
{54DE20BA-EBA7-4BF0-9BD9-F03766849716} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
{1223ED47-3E5E-4960-B70D-DFAF550F6666} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
{AF205715-C8B7-42EF-BF14-AFC9E7F27242} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
{68D1B955-1049-477B-A894-13FCB96C45DE} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC0FA8D3-6449-4FDA-BB46-ECF58FAD23B4}
Expand Down
10 changes: 10 additions & 0 deletions src/WorkflowCore/Interface/IWorkflowModifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,15 @@ IContainerStepBuilder<TData, Recur, TStepBody> Recur(Expression<Func<TData, Time
IStepBuilder<TData, Activity> Activity(string activityName, Expression<Func<TData, object>> parameters = null,
Expression<Func<TData, DateTime>> effectiveDate = null, Expression<Func<TData, bool>> cancelCondition = null);

/// <summary>
/// Wait here until an external activity is complete
/// </summary>
/// <param name="activityName">The name used to identify the activity to wait for</param>
/// <param name="parameters">The data to pass the external activity worker</param>
/// <param name="effectiveDate">Listen for events as of this effective date</param>
/// <param name="cancelCondition">A conditon that when true will cancel this WaitFor</param>
/// <returns></returns>
IStepBuilder<TData, Activity> Activity(Expression<Func<TData, IStepExecutionContext, string>> activityName, Expression<Func<TData, object>> parameters = null,
Expression<Func<TData, DateTime>> effectiveDate = null, Expression<Func<TData, bool>> cancelCondition = null);
}
}
19 changes: 19 additions & 0 deletions src/WorkflowCore/Services/FluentBuilders/StepBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -522,5 +522,24 @@ public IStepBuilder<TData, Activity> Activity(string activityName, Expression<Fu
Step.Outcomes.Add(new ValueOutcome { NextStep = newStep.Id });
return stepBuilder;
}

public IStepBuilder<TData, Activity> Activity(Expression<Func<TData, IStepExecutionContext, string>> activityName, Expression<Func<TData, object>> parameters = null, Expression<Func<TData, DateTime>> effectiveDate = null, Expression<Func<TData, bool>> cancelCondition = null)
{
var newStep = new WorkflowStep<Activity>();
newStep.CancelCondition = cancelCondition;

WorkflowBuilder.AddStep(newStep);
var stepBuilder = new StepBuilder<TData, Activity>(WorkflowBuilder, newStep);
stepBuilder.Input((step) => step.ActivityName, activityName);

if (parameters != null)
stepBuilder.Input((step) => step.Parameters, parameters);

if (effectiveDate != null)
stepBuilder.Input((step) => step.EffectiveDate, effectiveDate);

Step.Outcomes.Add(new ValueOutcome { NextStep = newStep.Id });
return stepBuilder;
}
}
}
4 changes: 4 additions & 0 deletions src/WorkflowCore/Services/FluentBuilders/WorkflowBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ public IStepBuilder<TData, Activity> Activity(string activityName, Expression<Fu
{
return Start().Activity(activityName, parameters, effectiveDate, cancelCondition);
}
public IStepBuilder<TData, Activity> Activity(Expression<Func<TData, IStepExecutionContext, string>> activityName, Expression<Func<TData, object>> parameters = null, Expression<Func<TData, DateTime>> effectiveDate = null, Expression<Func<TData, bool>> cancelCondition = null)
{
return Start().Activity(activityName, parameters, effectiveDate, cancelCondition);
}

private IStepBuilder<TData, InlineStepBody> Start()
{
Expand Down
29 changes: 29 additions & 0 deletions src/samples/WorkflowCore.Sample20/ActivityWorkflow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using WorkflowCore.Interface;
using WorkflowCore.Sample20.Steps;

namespace WorkflowCore.Sample20
{
class ActivityWorkflow : IWorkflow<MyData>
{
public string Id => "activity-sample";
public int Version => 1;

public void Build(IWorkflowBuilder<MyData> builder)
{
builder
.StartWith<HelloWorld>()
.Activity((data, context) => "get-approval-" + context.Workflow.Id, (data) => data.Request)
.Output(data => data.ApprovedBy, step => step.Result)
.Then<CustomMessage>()
.Input(step => step.Message, data => "Approved by " + data.ApprovedBy)
.Then<GoodbyeWorld>();
}
}

class MyData
{
public string Request { get; set; }
public string ApprovedBy { get; set; }
}
}
53 changes: 53 additions & 0 deletions src/samples/WorkflowCore.Sample20/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using WorkflowCore.Interface;

namespace WorkflowCore.Sample20
{
class Program
{
static void Main(string[] args)
{
var serviceProvider = ConfigureServices();

//start the workflow host
var host = serviceProvider.GetService<IWorkflowHost>();
host.RegisterWorkflow<ActivityWorkflow, MyData>();
host.Start();

Console.WriteLine("Starting workflow...");

var workflowId = host.StartWorkflow("activity-sample", new MyData { Request = "Spend $1,000,000" }).Result;

var approval = host.GetPendingActivity("get-approval-" + workflowId, "worker1", TimeSpan.FromMinutes(1)).Result;

if (approval != null)
{
Console.WriteLine("Approval required for " + approval.Parameters);
host.SubmitActivitySuccess(approval.Token, "John Smith");
}

Console.ReadLine();
host.Stop();
}

private static IServiceProvider ConfigureServices()
{
//setup dependency injection
IServiceCollection services = new ServiceCollection();
//services.AddWorkflow();
services.AddWorkflow(x => x.UseMongoDB(@"mongodb://localhost:27017", "workflow"));
//services.AddWorkflow(x => x.UseSqlServer(@"Server=.;Database=WorkflowCore;Trusted_Connection=True;", true, true));
//services.AddWorkflow(x => x.UsePostgreSQL(@"Server=127.0.0.1;Port=5432;Database=workflow;User Id=postgres;", true, true));
services.AddLogging(cfg =>
{
cfg.AddConsole();
cfg.AddDebug();
});

var serviceProvider = services.BuildServiceProvider();
return serviceProvider;
}
}
}
34 changes: 34 additions & 0 deletions src/samples/WorkflowCore.Sample20/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Activity sample

Illustrates how to have your workflow wait for an external activity that is fulfilled by a worker that you implement.

This workflow will wait for the `get-approval-{workflowId}` activity and pass the request string to it as an input.

The main reason behind of this example is Activities are global listeners. Therefore, in some cases, you want to be sure about its uniqueness.

Also, you can take look the issue https://github.com/danielgerlag/workflow-core/issues/542

```c#
builder
.StartWith<HelloWorld>()
.Activity((data, context) => context.Workflow.Id, (data) => data.Request)
.Output(data => data.ApprovedBy, step => step.Result)
.Then<CustomMessage>()
.Input(step => step.Message, data => "Approved by " + data.ApprovedBy)
.Then<GoodbyeWorld>();
```

Then we implement an activity worker to pull pending activities of type `get-approval`, where we can inspect the input and submit a response back to the waiting workflow.

```c#
var workflowId = host.StartWorkflow("activity-sample", new MyData { Request = "Spend $1,000,000" }).Result;

var approval = host.GetPendingActivity("get-approval-" + workflowId, "worker1", TimeSpan.FromMinutes(1)).Result;

if (approval != null)
{
Console.WriteLine("Approval required for " + approval.Parameters);
host.SubmitActivitySuccess(approval.Token, "John Smith");
}
```

19 changes: 19 additions & 0 deletions src/samples/WorkflowCore.Sample20/Steps/CustomMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Linq;
using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace WorkflowCore.Sample20.Steps
{
public class CustomMessage : StepBody
{

public string Message { get; set; }

public override ExecutionResult Run(IStepExecutionContext context)
{
Console.WriteLine(Message);
return ExecutionResult.Next();
}
}
}
16 changes: 16 additions & 0 deletions src/samples/WorkflowCore.Sample20/Steps/GoodbyeWorld.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Linq;
using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace WorkflowCore.Sample20.Steps
{
public class GoodbyeWorld : StepBody
{
public override ExecutionResult Run(IStepExecutionContext context)
{
Console.WriteLine("Goodbye world");
return ExecutionResult.Next();
}
}
}
16 changes: 16 additions & 0 deletions src/samples/WorkflowCore.Sample20/Steps/HelloWorld.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Linq;
using WorkflowCore.Interface;
using WorkflowCore.Models;

namespace WorkflowCore.Sample20.Steps
{
public class HelloWorld : StepBody
{
public override ExecutionResult Run(IStepExecutionContext context)
{
Console.WriteLine("Hello world");
return ExecutionResult.Next();
}
}
}
20 changes: 20 additions & 0 deletions src/samples/WorkflowCore.Sample20/WorkflowCore.Sample20.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\providers\WorkflowCore.Persistence.MongoDB\WorkflowCore.Persistence.MongoDB.csproj" />
<ProjectReference Include="..\..\WorkflowCore\WorkflowCore.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using WorkflowCore.Interface;
using WorkflowCore.Models;
using Xunit;
using FluentAssertions;
using System.Linq;
using WorkflowCore.Testing;

namespace WorkflowCore.IntegrationTests.Scenarios
{
public class ActivityScenario2 : WorkflowTest<ActivityScenario2.ActivityWorkflow, ActivityScenario2.MyDataClass>
{
public class MyDataClass
{
public object ActivityInput { get; set; }
public object ActivityOutput { get; set; }
}

public class ActivityInput
{
public string Value1 { get; set; }
public int Value2 { get; set; }
}

public class ActivityOutput
{
public string Value1 { get; set; }
public int Value2 { get; set; }
}

public class ActivityWorkflow : IWorkflow<MyDataClass>
{
public string Id => "ActivityWorkflow";
public int Version => 1;
public void Build(IWorkflowBuilder<MyDataClass> builder)
{
builder
.StartWith(context => ExecutionResult.Next())
.Activity((_, context) => "act-1-" + context.Workflow.Id, data => data.ActivityInput)
.Output(data => data.ActivityOutput, step => step.Result);
}
}

public ActivityScenario2()
{
Setup();
}

[Fact]
public void Scenario()
{
// compound key
var workflowId = StartWorkflow(new MyDataClass { ActivityInput = new ActivityInput { Value1 = "a", Value2 = 1 } });
var activity = Host.GetPendingActivity("act-1-" + workflowId, "worker1", TimeSpan.FromSeconds(30)).Result;

if (activity != null)
{
var actInput = (ActivityInput)activity.Parameters;
Host.SubmitActivitySuccess(activity.Token, new ActivityOutput
{
Value1 = actInput.Value1 + "1",
Value2 = actInput.Value2 + 1
});
}

WaitForWorkflowToComplete(workflowId, TimeSpan.FromSeconds(30));
GetStatus(workflowId).Should().Be(WorkflowStatus.Complete);
UnhandledStepErrors.Count.Should().Be(0);
GetData(workflowId).ActivityOutput.Should().BeOfType<ActivityOutput>();
var outData = (GetData(workflowId).ActivityOutput as ActivityOutput);
outData.Value1.Should().Be("a1");
outData.Value2.Should().Be(2);
}
}
}