diff --git a/src/Paramore.Brighter.MediatorWorkflow/Workflows.cs b/src/Paramore.Brighter.MediatorWorkflow/Workflows.cs
index d78c278f1..2b4896b33 100644
--- a/src/Paramore.Brighter.MediatorWorkflow/Workflows.cs
+++ b/src/Paramore.Brighter.MediatorWorkflow/Workflows.cs
@@ -34,7 +34,7 @@ namespace Paramore.Brighter.MediatorWorkflow;
/// The action to be taken with the step.
/// The action to be taken upon completion of the step.
/// The next step in the sequence.
-public record Step(string Name, IWorkflowAction Action, Action OnCompletion, Step? Next);
+public record Step(string Name, IWorkflowAction Action, Action OnCompletion, Step? Next, Action? OnFaulted = null, Step? FaultNext = null);
///
/// Defines an interface for workflow actions.
@@ -53,37 +53,24 @@ public interface IWorkflowAction
///
/// Represents a workflow based on evaluating a specification to determine which one to send
///
-/// The type of the true branch
-/// The type of the false branch
/// The rule that decides between the command issued by each branch
-///
-///
-///
-public class Choice(
- Func trueRequestFactory,
- Func falseRequestFactory,
+/// The workflow data, used to make the choice
+public class Choice(
+ Func> OnTrue,
+ Func> OnFalse,
ISpecification predicate
)
: IWorkflowAction
- where TTrueRequest : class, IRequest
- where TFalseRequest : class, IRequest
{
public void Handle(Workflow state, IAmACommandProcessor commandProcessor)
{
- //NOTE: we chose the command handler by parameterized type from the argument to Send() so the type needs to be explicit here
- // do not try to optimize this branch condition via a base type, it will not work
- if (predicate.IsSatisfiedBy(state.Data))
- {
- TTrueRequest command = trueRequestFactory();
- command.CorrelationId = state.Id;
- commandProcessor.Send(command);
- }
- else
+ if (state.CurrentStep is null)
+ throw new InvalidOperationException("The workflow has not been initialized.");
+
+ state.CurrentStep = state.CurrentStep with
{
- TFalseRequest command = falseRequestFactory();
- command.CorrelationId = state.Id;
- commandProcessor.Send(command);
- }
+ Next = (predicate.IsSatisfiedBy(state.Data) ? OnTrue(state.Data) : OnFalse(state.Data))
+ };
}
}
diff --git a/tests/Paramore.Brighter.Core.Tests/Workflows/TestDoubles/MyFault.cs b/tests/Paramore.Brighter.Core.Tests/Workflows/TestDoubles/MyFault.cs
new file mode 100644
index 000000000..b46dfab69
--- /dev/null
+++ b/tests/Paramore.Brighter.Core.Tests/Workflows/TestDoubles/MyFault.cs
@@ -0,0 +1,33 @@
+#region Licence
+/* The MIT License (MIT)
+Copyright © 2014 Ian Cooper
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the “Software”), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. */
+
+#endregion
+
+using System;
+
+namespace Paramore.Brighter.Core.Tests.Workflows.TestDoubles
+{
+ internal class MyFault(string? value) : Event(Guid.NewGuid().ToString())
+ {
+ public string Value { get; set; } = value ?? string.Empty;
+ }
+}
diff --git a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_failing_choice_workflow_step.cs b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_failing_choice_workflow_step.cs
index 6d69df292..e7ecbca88 100644
--- a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_failing_choice_workflow_step.cs
+++ b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_failing_choice_workflow_step.cs
@@ -13,6 +13,8 @@ public class MediatorFailingChoiceFlowTests
private readonly Mediator? _mediator;
private readonly Workflow _flow;
private bool _stepCompletedOne;
+ private bool _stepCompletedTwo;
+ private bool _stepCompletedThree;
public MediatorFailingChoiceFlowTests()
{
@@ -36,10 +38,20 @@ public MediatorFailingChoiceFlowTests()
var workflowData= new WorkflowTestData();
workflowData.Bag.Add("MyValue", "Fail");
- var stepOne = new Step("Test of Workflow Step One",
- new Choice(
- () => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! },
- () => new MyOtherCommand { Value = (workflowData.Bag["MyValue"] as string)! },
+ var stepThree = new Step("Test of Workflow Step Three",
+ new FireAndForget(() => new MyOtherCommand { Value = (workflowData.Bag["MyValue"] as string)! }),
+ () => { _stepCompletedThree = true; },
+ null);
+
+ var stepTwo = new Step("Test of Workflow Step Two",
+ new FireAndForget(() => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! }),
+ () => { _stepCompletedTwo = true; },
+ null);
+
+ var stepOne = new Step("Test of Workflow Step One",
+ new Choice(
+ (_) => stepTwo,
+ (_) => stepThree,
new Specification(x => x.Bag["MyValue"] as string == "Pass")),
() => { _stepCompletedOne = true; },
null);
@@ -61,6 +73,8 @@ public void When_running_a_choice_workflow_step()
_mediator?.RunWorkFlow(_flow);
_stepCompletedOne.Should().BeTrue();
+ _stepCompletedTwo.Should().BeFalse();
+ _stepCompletedThree.Should().BeTrue();
MyOtherCommandHandler.ReceivedCommands.Any(c => c.Value == "Fail").Should().BeTrue();
MyCommandHandler.ReceivedCommands.Any().Should().BeFalse();
_stepCompletedOne.Should().BeTrue();
diff --git a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_passing_choice_workflow_step.cs b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_passing_choice_workflow_step.cs
index 4af16bf8c..3a07565d0 100644
--- a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_passing_choice_workflow_step.cs
+++ b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_passing_choice_workflow_step.cs
@@ -13,6 +13,8 @@ public class MediatorPassingChoiceFlowTests
private readonly Mediator? _mediator;
private readonly Workflow _flow;
private bool _stepCompletedOne;
+ private bool _stepCompletedTwo;
+ private bool _stepCompletedThree;
public MediatorPassingChoiceFlowTests()
{
@@ -35,11 +37,21 @@ public MediatorPassingChoiceFlowTests()
var workflowData= new WorkflowTestData();
workflowData.Bag.Add("MyValue", "Pass");
+
+ var stepThree = new Step("Test of Workflow Step Three",
+ new FireAndForget(() => new MyOtherCommand { Value = (workflowData.Bag["MyValue"] as string)! }),
+ () => { _stepCompletedThree = true; },
+ null);
+
+ var stepTwo = new Step("Test of Workflow Step Two",
+ new FireAndForget(() => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! }),
+ () => { _stepCompletedTwo = true; },
+ null);
var stepOne = new Step("Test of Workflow Step One",
- new Choice(
- () => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! },
- () => new MyOtherCommand { Value = (workflowData.Bag["MyValue"] as string)! },
+ new Choice(
+ (_) => stepTwo,
+ (_) => stepThree,
new Specification(x => x.Bag["MyValue"] as string == "Pass")),
() => { _stepCompletedOne = true; },
null);
diff --git a/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_workflow_with_robust_reply_nofault.cs b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_workflow_with_robust_reply_nofault.cs
new file mode 100644
index 000000000..db396da88
--- /dev/null
+++ b/tests/Paramore.Brighter.Core.Tests/Workflows/When_running_a_workflow_with_robust_reply_nofault.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+using Amazon.Runtime.Internal.Transform;
+using FluentAssertions;
+using Paramore.Brighter.Core.Tests.Workflows.TestDoubles;
+using Paramore.Brighter.MediatorWorkflow;
+using Polly.Registry;
+using Xunit;
+
+namespace Paramore.Brighter.Core.Tests.Workflows;
+
+public class MediatorRobustReplyNoFaultStepFlowTests
+{
+ private readonly Mediator _mediator;
+ private bool _stepCompleted;
+ private bool _stepFaulted;
+ private readonly Workflow _flow;
+
+ public MediatorRobustReplyNoFaultStepFlowTests()
+ {
+ var registry = new SubscriberRegistry();
+ registry.Register();
+ registry.Register();
+
+ IAmACommandProcessor commandProcessor = null;
+ var handlerFactory = new SimpleHandlerFactorySync((handlerType) =>
+ handlerType switch
+ {
+ _ when handlerType == typeof(MyCommandHandler) => new MyCommandHandler(commandProcessor),
+ _ when handlerType == typeof(MyEventHandler) => new MyEventHandler(_mediator),
+ _ => throw new InvalidOperationException($"The handler type {handlerType} is not supported")
+ });
+
+ commandProcessor = new CommandProcessor(registry, handlerFactory, new InMemoryRequestContextFactory(), new PolicyRegistry());
+ PipelineBuilder.ClearPipelineCache();
+
+ var workflowData= new WorkflowTestData();
+ workflowData.Bag.Add("MyValue", "Test");
+
+ var firstStep = new Step("Test of Workflow",
+ new RobustRequestAndReaction(
+ () => new MyCommand { Value = (workflowData.Bag["MyValue"] as string)! },
+ (reply) => workflowData.Bag.Add("MyReply", ((MyEvent)reply).Value),
+ (fault) => workflowData.Bag.Add("MyFault", ((MyFault)fault).Value)),
+ () => { _stepCompleted = true; },
+ null,
+ () => { _stepFaulted = true; },
+ null);
+
+ _flow = new Workflow(firstStep, workflowData) ;
+
+ _mediator = new Mediator(
+ commandProcessor,
+ new InMemoryWorkflowStore()
+ );
+ }
+
+ [Fact]
+ public void When_running_a_workflow_with_reply()
+ {
+ MyCommandHandler.ReceivedCommands.Clear();
+ MyEventHandler.ReceivedEvents.Clear();
+
+ _mediator.RunWorkFlow(_flow);
+
+ _stepCompleted.Should().BeTrue();
+ _stepFaulted.Should().BeFalse();
+
+ MyCommandHandler.ReceivedCommands.Any(c => c.Value == "Test").Should().BeTrue();
+ MyEventHandler.ReceivedEvents.Any(e => e.Value == "Test").Should().BeTrue();
+ _flow.State.Should().Be(WorkflowState.Done);
+ }
+}