-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Closed as not planned
Closed as not planned
Copy link
Description
After the first exception (see example code), the GetOpenUserActions
function returns two user actions: start
, and step1
. Is it a misuse on my side or do "jumps" not work with saga transactions? The jumps were mentioned in issue #167.
Example code:
using System;
using System.Collections.Generic;
using WorkflowCore.Interface;
using WorkflowCore.Models;
using Xunit;
using FluentAssertions;
using System.Linq;
using System.Threading.Tasks;
using WorkflowCore.Testing;
using WorkflowCore.Users.Models;
namespace WorkflowCore.IntegrationTests.Scenarios
{
public class RetrySagaWithThreeUserTaskScenario : WorkflowTest<RetrySagaWithThreeUserTaskScenario.Workflow, RetrySagaWithThreeUserTaskScenario.MyDataClass>
{
private static readonly ExpectedValues[] _expectedValues = new[]
{
new ExpectedValues("start", "start", "Step 1", "step1"),
new ExpectedValues("step1", "step1", "Step 2", "step2"),
new ExpectedValues("step1", "step1", "Step 2", "step2"),
new ExpectedValues("step1", "step1", "Step 2", "step2"),
new ExpectedValues("step2", "step2", "Step 3", "step3"),
};
public class MyDataClass
{
}
public class Workflow : IWorkflow<MyDataClass>
{
public static int Event1Fired;
public static int Event2Fired;
public static int Event2Passed;
public static int Event3Fired;
public static int TailEventFired;
public static int StartCompensation1Fired;
public static int StartCompensation2Fired;
public static int StartCompensation3Fired;
public static int Compensation1Fired;
public static int Compensation2Fired;
public static int Compensation3Fired;
public string Id => "RetrySagaWithThreeUserTasksWorkflow";
public int Version => 1;
public void Build(IWorkflowBuilder<MyDataClass> builder)
{
var start = builder
.StartWith(_ => ExecutionResult.Next());
var step1 = builder
.StartWith(_ => ExecutionResult.Next());
var step2 = builder
.StartWith(_ => ExecutionResult.Next());
var step3 = builder
.StartWith(_ => ExecutionResult.Next());
start
.CompensateWith(_ => StartCompensation1Fired++)
.Saga(x => x
.StartWith(_ => ExecutionResult.Next())
.UserTask("start", _ => "start")
.WithOption("step1", "Step 1")
.Do(wb => wb
.StartWith(_ => ExecutionResult.Next())
.Then(_ => Event1Fired++)
.Then(step1))
.CompensateWith(_ => Compensation1Fired++))
.OnError(WorkflowErrorHandling.Retry, TimeSpan.Zero);
step1
.CompensateWith(_ => StartCompensation2Fired++)
.Saga(x => x
.StartWith(_ => ExecutionResult.Next())
.UserTask("step1", _ => "step1")
.WithOption("step2", "Step 2")
.Do(wb => wb
.StartWith(_ => ExecutionResult.Next())
.Then(_ =>
{
Event2Fired++;
if (Event2Fired < 3)
throw new Exception();
Event2Passed++;
})
.Then(step2))
.CompensateWith(_ => Compensation2Fired++))
.OnError(WorkflowErrorHandling.Retry, TimeSpan.Zero);
step2
.CompensateWith(_ => StartCompensation3Fired++)
.Saga(x => x
.StartWith(_ => ExecutionResult.Next())
.UserTask("step2", _ => "step2")
.WithOption("step3", "Step 3")
.Do(wb => wb
.StartWith(_ => ExecutionResult.Next())
.Then(_ => Event3Fired++)
.Then(step3))
.CompensateWith(_ => Compensation3Fired++))
.OnError(WorkflowErrorHandling.Retry, TimeSpan.Zero);
step3
.Then(_ => TailEventFired++);
}
}
public RetrySagaWithThreeUserTaskScenario()
{
Setup();
Workflow.Event1Fired = 0;
Workflow.Event2Fired = 0;
Workflow.Event2Passed = 0;
Workflow.Event3Fired = 0;
Workflow.StartCompensation1Fired = 0;
Workflow.StartCompensation2Fired = 0;
Workflow.StartCompensation3Fired = 0;
Workflow.Compensation1Fired = 0;
Workflow.Compensation2Fired = 0;
Workflow.Compensation3Fired = 0;
Workflow.TailEventFired = 0;
}
[Fact]
public async Task Scenario()
{
var workflowId = StartWorkflow(new MyDataClass());
var instance = await Host.PersistenceStore.GetWorkflowInstance(workflowId);
string oldUserOptionKey = null;
foreach (var expectedValue in _expectedValues)
{
var userOptions = await WaitForDifferentUserStepAsync(instance, TimeSpan.FromSeconds(1), oldUserOptionKey);
userOptions.Count.Should().Be(1);
var userOption = userOptions.Single();
userOption.Prompt.Should().Be(expectedValue.Prompt);
userOption.AssignedPrincipal.Should().Be(expectedValue.AssignedPrincipal);
userOption.Options.Count.Should().Be(1);
var selectionOption = userOption.Options.Single();
selectionOption.Key.Should().Be(expectedValue.OptionKey);
selectionOption.Value.Should().Be(expectedValue.OptionValue);
await Host.PublishUserAction(userOption.Key, string.Empty, selectionOption.Value);
oldUserOptionKey = userOption.Key;
}
WaitForWorkflowToComplete(workflowId, TimeSpan.FromSeconds(30));
GetStatus(workflowId).Should().Be(WorkflowStatus.Complete);
UnhandledStepErrors.Count.Should().Be(2);
Workflow.Event1Fired.Should().Be(1);
Workflow.Event2Fired.Should().Be(3);
Workflow.Event2Passed.Should().Be(1);
Workflow.Event3Fired.Should().Be(1);
Workflow.StartCompensation1Fired.Should().Be(0);
Workflow.StartCompensation2Fired.Should().Be(0);
Workflow.StartCompensation3Fired.Should().Be(0);
Workflow.Compensation1Fired.Should().Be(0);
Workflow.Compensation2Fired.Should().Be(2);
Workflow.Compensation3Fired.Should().Be(0);
Workflow.TailEventFired.Should().Be(1);
}
private static async Task<IReadOnlyCollection<OpenUserAction>> WaitForDifferentUserStepAsync(
WorkflowInstance instance,
TimeSpan timeout,
string oldUserActionKey = null)
{
var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime <= timeout)
{
var userActions = await WaitForUserStepAsync(instance);
if (oldUserActionKey != null && userActions.Any(x => x.Key == oldUserActionKey))
{
continue;
}
return userActions;
}
return Array.Empty<OpenUserAction>();
}
private static async Task<IReadOnlyCollection<OpenUserAction>> WaitForUserStepAsync(WorkflowInstance instance)
{
var delayCount = 200;
var openActions = instance.GetOpenUserActions()?.ToList();
while ((openActions?.Count ?? 0) == 0)
{
await Task.Delay(TimeSpan.FromMilliseconds(10));
openActions = instance.GetOpenUserActions()?.ToList();
if (delayCount-- == 0)
{
break;
}
}
return openActions;
}
private class ExpectedValues
{
public ExpectedValues(string prompt, string assignedPrincipal, string optionKey, string optionValue)
{
Prompt = prompt;
AssignedPrincipal = assignedPrincipal;
OptionKey = optionKey;
OptionValue = optionValue;
}
public string Prompt { get; }
public string AssignedPrincipal { get; }
public string OptionKey { get; }
public string OptionValue { get; }
}
}
}
Metadata
Metadata
Assignees
Labels
No labels