diff --git a/src/DIT.Workflower.DependencyInjection/Abstractions/IWorkflow.cs b/src/DIT.Workflower.DependencyInjection/Abstractions/IWorkflow.cs index 48432b9..598a95c 100644 --- a/src/DIT.Workflower.DependencyInjection/Abstractions/IWorkflow.cs +++ b/src/DIT.Workflower.DependencyInjection/Abstractions/IWorkflow.cs @@ -4,8 +4,12 @@ public interface IWorkflow where TState : struct where TCommand : struct { + string Id { get; } + int Version { get; } + string Reference => $"{Id}.v{Version}"; + /// /// Gets a list of allowed transitions without any condition checks. /// diff --git a/src/DIT.Workflower.DependencyInjection/Abstractions/IWorkflowFactory.cs b/src/DIT.Workflower.DependencyInjection/Abstractions/IWorkflowFactory.cs index 2124641..8503f29 100644 --- a/src/DIT.Workflower.DependencyInjection/Abstractions/IWorkflowFactory.cs +++ b/src/DIT.Workflower.DependencyInjection/Abstractions/IWorkflowFactory.cs @@ -5,8 +5,8 @@ public interface IWorkflowFactory where TCommand : struct { - public IWorkflow CreateWorkflow(); + public IWorkflow CreateWorkflow(string id); - public IWorkflow CreateWorkflow(int version); + public IWorkflow CreateWorkflow(string id, int version); } diff --git a/src/DIT.Workflower.DependencyInjection/DefaultWorkflowFactory.cs b/src/DIT.Workflower.DependencyInjection/DefaultWorkflowFactory.cs index 1001df2..dfe6012 100644 --- a/src/DIT.Workflower.DependencyInjection/DefaultWorkflowFactory.cs +++ b/src/DIT.Workflower.DependencyInjection/DefaultWorkflowFactory.cs @@ -1,5 +1,4 @@ - -namespace DIT.Workflower.DependencyInjection; +namespace DIT.Workflower.DependencyInjection; public class DefaultWorkflowFactory : IWorkflowFactory where TState : struct @@ -13,16 +12,17 @@ public class DefaultWorkflowFactory : IWorkflowFacto _serviceProvider = sp; } - public IWorkflow CreateWorkflow() - => CreateWorkflow(version: 1); + public IWorkflow CreateWorkflow(string id) + => CreateWorkflow(id, version: 1); - public IWorkflow CreateWorkflow(int version) + public IWorkflow CreateWorkflow(string id, int version) { + var reference = $"{id}.v{version}"; var service = _serviceProvider.GetServices>() - .FirstOrDefault(x => x.Version == version); + .FirstOrDefault(x => x.Reference == reference); if (service is null) - throw new ArgumentOutOfRangeException(nameof(version), $"Version {version} of workflow does not exist"); + throw new ArgumentOutOfRangeException(nameof(version), $"Workflow reference {id}.v{version} does not exist"); return service; } diff --git a/src/DIT.Workflower.DependencyInjection/Extensions/IServiceCollectionExtensions.cs b/src/DIT.Workflower.DependencyInjection/Extensions/IServiceCollectionExtensions.cs index 57cf280..42bced4 100644 --- a/src/DIT.Workflower.DependencyInjection/Extensions/IServiceCollectionExtensions.cs +++ b/src/DIT.Workflower.DependencyInjection/Extensions/IServiceCollectionExtensions.cs @@ -5,25 +5,25 @@ namespace DIT.Workflower.DependencyInjection.Extensions; public static class IServiceCollectionExtensions { - public static ITransitionOn AddWorkflowDefinition(this IServiceCollection services, TState initial) + public static ITransitionStart AddWorkflowDefinition(this IServiceCollection services, string id) where TState : struct where TCommand : struct { - return AddWorkflowDefinition(services, initial, version: 1); + return AddWorkflowDefinition(services, id, version: 1); } - public static ITransitionOn AddWorkflowDefinition(this IServiceCollection services, TState initial, int version) + public static ITransitionStart AddWorkflowDefinition(this IServiceCollection services, string id, int version) where TState : struct where TCommand : struct { - var builder = WorkflowDefinitionBuilder.Initial(initial); + var builder = WorkflowDefinitionBuilder.Create(); services.TryAddSingleton, DefaultWorkflowFactory>(); services.AddSingleton, WorkflowDefinitionWrapper>(sp => { - var definition = ((WorkflowDefinitionBuilder)builder); - var wrapper = new WorkflowDefinitionWrapper(definition, version); + var definition = (WorkflowDefinitionBuilder)builder; + var wrapper = new WorkflowDefinitionWrapper(definition, id, version); return wrapper; }); diff --git a/src/DIT.Workflower.DependencyInjection/WorkflowDefinitionWrapper.cs b/src/DIT.Workflower.DependencyInjection/WorkflowDefinitionWrapper.cs index 31a917f..334fa51 100644 --- a/src/DIT.Workflower.DependencyInjection/WorkflowDefinitionWrapper.cs +++ b/src/DIT.Workflower.DependencyInjection/WorkflowDefinitionWrapper.cs @@ -5,11 +5,14 @@ public record WorkflowDefinitionWrapper : WorkflowDe where TCommand : struct { + public string Id { get; } + public int Version { get; } - public WorkflowDefinitionWrapper(WorkflowDefinitionBuilder builder, int version) + public WorkflowDefinitionWrapper(WorkflowDefinitionBuilder builder, string id, int version) : base(builder.Transitions) { + Id = id; Version = version; } diff --git a/src/DIT.Workflower/Abstractions.cs b/src/DIT.Workflower/Abstractions.cs index 179b142..a575804 100644 --- a/src/DIT.Workflower/Abstractions.cs +++ b/src/DIT.Workflower/Abstractions.cs @@ -1,5 +1,12 @@ namespace DIT.Workflower.Abstractions; +public interface ITransitionStart + where TState : struct + where TCommand : struct +{ + ITransitionOn From(in TState state); +} + public interface ITransitionOn where TState : struct where TCommand : struct @@ -24,7 +31,7 @@ public interface ITransitionCondition : ITransitionE } -public interface ITransitionDone : ITransitionOn +public interface ITransitionDone where TState : struct where TCommand : struct { diff --git a/src/DIT.Workflower/WorkflowDefinitionBuilder.cs b/src/DIT.Workflower/WorkflowDefinitionBuilder.cs index 97eba4b..5a00a4f 100644 --- a/src/DIT.Workflower/WorkflowDefinitionBuilder.cs +++ b/src/DIT.Workflower/WorkflowDefinitionBuilder.cs @@ -2,7 +2,8 @@ namespace DIT.Workflower; -public sealed class WorkflowDefinitionBuilder : +public sealed class WorkflowDefinitionBuilder : + ITransitionStart, ITransitionOn, ITransitionExit, ITransitionCondition, @@ -17,11 +18,11 @@ public sealed class WorkflowDefinitionBuilder : #region Constructor - private WorkflowDefinitionBuilder(TState initialState) - => _current = new() { From = initialState }; - - public static ITransitionOn Initial(TState initialState) - => new WorkflowDefinitionBuilder(initialState); + private WorkflowDefinitionBuilder() + => _current = new(); + + public static ITransitionStart Create() + => new WorkflowDefinitionBuilder(); #endregion @@ -69,8 +70,6 @@ public sealed class WorkflowDefinitionBuilder : public WorkflowDefinition Build() { - Transitions.Add(_current); - if (!Transitions.Any()) throw new InvalidOperationException("No transitions are added"); diff --git a/tests/DIT.Workflower.Tests/DependencyInjection/DependencyInjectionTests.cs b/tests/DIT.Workflower.Tests/DependencyInjection/DependencyInjectionTests.cs index f202f22..ffc7dc0 100644 --- a/tests/DIT.Workflower.Tests/DependencyInjection/DependencyInjectionTests.cs +++ b/tests/DIT.Workflower.Tests/DependencyInjection/DependencyInjectionTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using DIT.Workflower.DependencyInjection.Abstractions; +using DIT.Workflower.DependencyInjection.Abstractions; using DIT.Workflower.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection; @@ -17,8 +12,10 @@ public class DependencyInjectionTests { var sc = new ServiceCollection(); + var id = "test"; - sc.AddWorkflowDefinition(initial: PhoneState.Idle, version: 1) + sc.AddWorkflowDefinition(id, version: 1) + .From(PhoneState.Idle) .On(PhoneCommand.IncomingCall) .To(PhoneState.Ringing) @@ -27,7 +24,8 @@ public class DependencyInjectionTests .To(PhoneState.Connected) ; - sc.AddWorkflowDefinition(initial: PhoneState.Idle, version: 2) + sc.AddWorkflowDefinition(id, version: 2) + .From(PhoneState.Idle) .On(PhoneCommand.IncomingCall) .To(PhoneState.Ringing) @@ -42,9 +40,11 @@ public class DependencyInjectionTests var sp = sc.BuildServiceProvider(); - var workflowFactory = sp.GetService>(); - var v1 = workflowFactory?.CreateWorkflow(); - var v2 = workflowFactory?.CreateWorkflow(version: 2); + var workflowFactory = sp.GetService>()!; + + + var v1 = workflowFactory.CreateWorkflow(id); + var v2 = workflowFactory.CreateWorkflow(id, version: 2); Assert.NotNull(workflowFactory); Assert.NotNull(v1); diff --git a/tests/DIT.Workflower.Tests/WorkflowConditionTests.cs b/tests/DIT.Workflower.Tests/WorkflowConditionTests.cs index 60a7ecf..4b38ea9 100644 --- a/tests/DIT.Workflower.Tests/WorkflowConditionTests.cs +++ b/tests/DIT.Workflower.Tests/WorkflowConditionTests.cs @@ -2,9 +2,9 @@ namespace DIT.Workflower.Tests; public class WorkflowConditionTests { - private ITransitionOn GetDefaultBuilder() + private static ITransitionStart GetDefaultBuilder() { - return WorkflowDefinitionBuilder.Initial(PhoneState.Idle); + return WorkflowDefinitionBuilder.Create(); } [Fact] @@ -15,17 +15,19 @@ public class WorkflowConditionTests var a = "b"; var builder1 = GetDefaultBuilder() + .From(PhoneState.Ringing) .On(PhoneCommand.Decline) .When((res) => a == "n") .To(PhoneState.Declined); var builder2 = GetDefaultBuilder() + .From(PhoneState.Ringing) .On(PhoneCommand.Decline) .When((res) => a == "b" && res.Active is false) .To(PhoneState.OnHold); - Assert.Empty(builder1.Build().GetAllowedTransitions(phone, PhoneState.Idle)); - Assert.Single(builder2.Build().GetAllowedTransitions(phone, PhoneState.Idle)); + Assert.Empty(builder1.Build().GetAllowedTransitions(phone, PhoneState.Ringing)); + Assert.Single(builder2.Build().GetAllowedTransitions(phone, PhoneState.Ringing)); } [Fact] @@ -37,18 +39,20 @@ public class WorkflowConditionTests var other = a; var builder1 = GetDefaultBuilder() + .From(PhoneState.OnHold) .On(PhoneCommand.Resume) .When((res) => a == "c") .When((res) => other == a) .To(PhoneState.Connected); var builder2 = GetDefaultBuilder() + .From(PhoneState.OnHold) .On(PhoneCommand.Resume) .When((res) => a == "b") .When((res) => other == a) .To(PhoneState.Connected); - Assert.Empty(builder1.Build().GetAllowedTransitions(phone, PhoneState.Idle)); - Assert.Single(builder2.Build().GetAllowedTransitions(phone, PhoneState.Idle)); + Assert.Empty(builder1.Build().GetAllowedTransitions(phone, from: PhoneState.OnHold)); + Assert.Single(builder2.Build().GetAllowedTransitions(phone, from: PhoneState.OnHold)); } }