General improvements

This commit is contained in:
Shkar T. Noori 2022-05-31 12:58:38 +00:00
parent d1f669f2c5
commit 98e02edb9c
No known key found for this signature in database
GPG Key ID: E7AD76088FB6FE02
9 changed files with 59 additions and 42 deletions

View File

@ -4,8 +4,12 @@ public interface IWorkflow<TState, TCommand, TContext>
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
string Id { get; }
int Version { get; } int Version { get; }
string Reference => $"{Id}.v{Version}";
/// <summary> /// <summary>
/// Gets a list of allowed transitions without any condition checks. /// Gets a list of allowed transitions without any condition checks.
/// </summary> /// </summary>

View File

@ -5,8 +5,8 @@ public interface IWorkflowFactory<TState, TCommand, TContext>
where TCommand : struct where TCommand : struct
{ {
public IWorkflow<TState, TCommand, TContext> CreateWorkflow(); public IWorkflow<TState, TCommand, TContext> CreateWorkflow(string id);
public IWorkflow<TState, TCommand, TContext> CreateWorkflow(int version); public IWorkflow<TState, TCommand, TContext> CreateWorkflow(string id, int version);
} }

View File

@ -1,5 +1,4 @@
 namespace DIT.Workflower.DependencyInjection;
namespace DIT.Workflower.DependencyInjection;
public class DefaultWorkflowFactory<TState, TCommand, TContext> : IWorkflowFactory<TState, TCommand, TContext> public class DefaultWorkflowFactory<TState, TCommand, TContext> : IWorkflowFactory<TState, TCommand, TContext>
where TState : struct where TState : struct
@ -13,16 +12,17 @@ public class DefaultWorkflowFactory<TState, TCommand, TContext> : IWorkflowFacto
_serviceProvider = sp; _serviceProvider = sp;
} }
public IWorkflow<TState, TCommand, TContext> CreateWorkflow() public IWorkflow<TState, TCommand, TContext> CreateWorkflow(string id)
=> CreateWorkflow(version: 1); => CreateWorkflow(id, version: 1);
public IWorkflow<TState, TCommand, TContext> CreateWorkflow(int version) public IWorkflow<TState, TCommand, TContext> CreateWorkflow(string id, int version)
{ {
var reference = $"{id}.v{version}";
var service = _serviceProvider.GetServices<IWorkflow<TState, TCommand, TContext>>() var service = _serviceProvider.GetServices<IWorkflow<TState, TCommand, TContext>>()
.FirstOrDefault(x => x.Version == version); .FirstOrDefault(x => x.Reference == reference);
if (service is null) 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; return service;
} }

View File

@ -5,25 +5,25 @@ namespace DIT.Workflower.DependencyInjection.Extensions;
public static class IServiceCollectionExtensions public static class IServiceCollectionExtensions
{ {
public static ITransitionOn<TState, TCommand, TContext> AddWorkflowDefinition<TState, TCommand, TContext>(this IServiceCollection services, TState initial) public static ITransitionStart<TState, TCommand, TContext> AddWorkflowDefinition<TState, TCommand, TContext>(this IServiceCollection services, string id)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
return AddWorkflowDefinition<TState, TCommand, TContext>(services, initial, version: 1); return AddWorkflowDefinition<TState, TCommand, TContext>(services, id, version: 1);
} }
public static ITransitionOn<TState, TCommand, TContext> AddWorkflowDefinition<TState, TCommand, TContext>(this IServiceCollection services, TState initial, int version) public static ITransitionStart<TState, TCommand, TContext> AddWorkflowDefinition<TState, TCommand, TContext>(this IServiceCollection services, string id, int version)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
var builder = WorkflowDefinitionBuilder<TState, TCommand, TContext>.Initial(initial); var builder = WorkflowDefinitionBuilder<TState, TCommand, TContext>.Create();
services.TryAddSingleton<IWorkflowFactory<TState, TCommand, TContext>, DefaultWorkflowFactory<TState, TCommand, TContext>>(); services.TryAddSingleton<IWorkflowFactory<TState, TCommand, TContext>, DefaultWorkflowFactory<TState, TCommand, TContext>>();
services.AddSingleton<IWorkflow<TState, TCommand, TContext>, WorkflowDefinitionWrapper<TState, TCommand, TContext>>(sp => services.AddSingleton<IWorkflow<TState, TCommand, TContext>, WorkflowDefinitionWrapper<TState, TCommand, TContext>>(sp =>
{ {
var definition = ((WorkflowDefinitionBuilder<TState, TCommand, TContext>)builder); var definition = (WorkflowDefinitionBuilder<TState, TCommand, TContext>)builder;
var wrapper = new WorkflowDefinitionWrapper<TState, TCommand, TContext>(definition, version); var wrapper = new WorkflowDefinitionWrapper<TState, TCommand, TContext>(definition, id, version);
return wrapper; return wrapper;
}); });

View File

@ -5,11 +5,14 @@ public record WorkflowDefinitionWrapper<TState, TCommand, TContext> : WorkflowDe
where TCommand : struct where TCommand : struct
{ {
public string Id { get; }
public int Version { get; } public int Version { get; }
public WorkflowDefinitionWrapper(WorkflowDefinitionBuilder<TState, TCommand, TContext> builder, int version) public WorkflowDefinitionWrapper(WorkflowDefinitionBuilder<TState, TCommand, TContext> builder, string id, int version)
: base(builder.Transitions) : base(builder.Transitions)
{ {
Id = id;
Version = version; Version = version;
} }

View File

@ -1,5 +1,12 @@
namespace DIT.Workflower.Abstractions; namespace DIT.Workflower.Abstractions;
public interface ITransitionStart<TState, TCommand, TContext>
where TState : struct
where TCommand : struct
{
ITransitionOn<TState, TCommand, TContext> From(in TState state);
}
public interface ITransitionOn<TState, TCommand, TContext> public interface ITransitionOn<TState, TCommand, TContext>
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
@ -24,7 +31,7 @@ public interface ITransitionCondition<TState, TCommand, TContext> : ITransitionE
} }
public interface ITransitionDone<TState, TCommand, TContext> : ITransitionOn<TState, TCommand, TContext> public interface ITransitionDone<TState, TCommand, TContext>
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {

View File

@ -2,7 +2,8 @@
namespace DIT.Workflower; namespace DIT.Workflower;
public sealed class WorkflowDefinitionBuilder<TState, TCommand, TContext> : public sealed class WorkflowDefinitionBuilder<TState, TCommand, TContext> :
ITransitionStart<TState, TCommand, TContext>,
ITransitionOn<TState, TCommand, TContext>, ITransitionOn<TState, TCommand, TContext>,
ITransitionExit<TState, TCommand, TContext>, ITransitionExit<TState, TCommand, TContext>,
ITransitionCondition<TState, TCommand, TContext>, ITransitionCondition<TState, TCommand, TContext>,
@ -17,11 +18,11 @@ public sealed class WorkflowDefinitionBuilder<TState, TCommand, TContext> :
#region Constructor #region Constructor
private WorkflowDefinitionBuilder(TState initialState) private WorkflowDefinitionBuilder()
=> _current = new() { From = initialState }; => _current = new();
public static ITransitionOn<TState, TCommand, TContext> Initial(TState initialState) public static ITransitionStart<TState, TCommand, TContext> Create()
=> new WorkflowDefinitionBuilder<TState, TCommand, TContext>(initialState); => new WorkflowDefinitionBuilder<TState, TCommand, TContext>();
#endregion #endregion
@ -69,8 +70,6 @@ public sealed class WorkflowDefinitionBuilder<TState, TCommand, TContext> :
public WorkflowDefinition<TState, TCommand, TContext> Build() public WorkflowDefinition<TState, TCommand, TContext> Build()
{ {
Transitions.Add(_current);
if (!Transitions.Any()) if (!Transitions.Any())
throw new InvalidOperationException("No transitions are added"); throw new InvalidOperationException("No transitions are added");

View File

@ -1,9 +1,4 @@
using System; using DIT.Workflower.DependencyInjection.Abstractions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DIT.Workflower.DependencyInjection.Abstractions;
using DIT.Workflower.DependencyInjection.Extensions; using DIT.Workflower.DependencyInjection.Extensions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -17,8 +12,10 @@ public class DependencyInjectionTests
{ {
var sc = new ServiceCollection(); var sc = new ServiceCollection();
var id = "test";
sc.AddWorkflowDefinition<PhoneState, PhoneCommand, PhoneCall>(initial: PhoneState.Idle, version: 1) sc.AddWorkflowDefinition<PhoneState, PhoneCommand, PhoneCall>(id, version: 1)
.From(PhoneState.Idle)
.On(PhoneCommand.IncomingCall) .On(PhoneCommand.IncomingCall)
.To(PhoneState.Ringing) .To(PhoneState.Ringing)
@ -27,7 +24,8 @@ public class DependencyInjectionTests
.To(PhoneState.Connected) .To(PhoneState.Connected)
; ;
sc.AddWorkflowDefinition<PhoneState, PhoneCommand, PhoneCall>(initial: PhoneState.Idle, version: 2) sc.AddWorkflowDefinition<PhoneState, PhoneCommand, PhoneCall>(id, version: 2)
.From(PhoneState.Idle)
.On(PhoneCommand.IncomingCall) .On(PhoneCommand.IncomingCall)
.To(PhoneState.Ringing) .To(PhoneState.Ringing)
@ -42,9 +40,11 @@ public class DependencyInjectionTests
var sp = sc.BuildServiceProvider(); var sp = sc.BuildServiceProvider();
var workflowFactory = sp.GetService<IWorkflowFactory<PhoneState, PhoneCommand, PhoneCall>>(); var workflowFactory = sp.GetService<IWorkflowFactory<PhoneState, PhoneCommand, PhoneCall>>()!;
var v1 = workflowFactory?.CreateWorkflow();
var v2 = workflowFactory?.CreateWorkflow(version: 2);
var v1 = workflowFactory.CreateWorkflow(id);
var v2 = workflowFactory.CreateWorkflow(id, version: 2);
Assert.NotNull(workflowFactory); Assert.NotNull(workflowFactory);
Assert.NotNull(v1); Assert.NotNull(v1);

View File

@ -2,9 +2,9 @@ namespace DIT.Workflower.Tests;
public class WorkflowConditionTests public class WorkflowConditionTests
{ {
private ITransitionOn<PhoneState, PhoneCommand, PhoneCall> GetDefaultBuilder() private static ITransitionStart<PhoneState, PhoneCommand, PhoneCall> GetDefaultBuilder()
{ {
return WorkflowDefinitionBuilder<PhoneState, PhoneCommand, PhoneCall>.Initial(PhoneState.Idle); return WorkflowDefinitionBuilder<PhoneState, PhoneCommand, PhoneCall>.Create();
} }
[Fact] [Fact]
@ -15,17 +15,19 @@ public class WorkflowConditionTests
var a = "b"; var a = "b";
var builder1 = GetDefaultBuilder() var builder1 = GetDefaultBuilder()
.From(PhoneState.Ringing)
.On(PhoneCommand.Decline) .On(PhoneCommand.Decline)
.When((res) => a == "n") .When((res) => a == "n")
.To(PhoneState.Declined); .To(PhoneState.Declined);
var builder2 = GetDefaultBuilder() var builder2 = GetDefaultBuilder()
.From(PhoneState.Ringing)
.On(PhoneCommand.Decline) .On(PhoneCommand.Decline)
.When((res) => a == "b" && res.Active is false) .When((res) => a == "b" && res.Active is false)
.To(PhoneState.OnHold); .To(PhoneState.OnHold);
Assert.Empty(builder1.Build().GetAllowedTransitions(phone, PhoneState.Idle)); Assert.Empty(builder1.Build().GetAllowedTransitions(phone, PhoneState.Ringing));
Assert.Single(builder2.Build().GetAllowedTransitions(phone, PhoneState.Idle)); Assert.Single(builder2.Build().GetAllowedTransitions(phone, PhoneState.Ringing));
} }
[Fact] [Fact]
@ -37,18 +39,20 @@ public class WorkflowConditionTests
var other = a; var other = a;
var builder1 = GetDefaultBuilder() var builder1 = GetDefaultBuilder()
.From(PhoneState.OnHold)
.On(PhoneCommand.Resume) .On(PhoneCommand.Resume)
.When((res) => a == "c") .When((res) => a == "c")
.When((res) => other == a) .When((res) => other == a)
.To(PhoneState.Connected); .To(PhoneState.Connected);
var builder2 = GetDefaultBuilder() var builder2 = GetDefaultBuilder()
.From(PhoneState.OnHold)
.On(PhoneCommand.Resume) .On(PhoneCommand.Resume)
.When((res) => a == "b") .When((res) => a == "b")
.When((res) => other == a) .When((res) => other == a)
.To(PhoneState.Connected); .To(PhoneState.Connected);
Assert.Empty(builder1.Build().GetAllowedTransitions(phone, PhoneState.Idle)); Assert.Empty(builder1.Build().GetAllowedTransitions(phone, from: PhoneState.OnHold));
Assert.Single(builder2.Build().GetAllowedTransitions(phone, PhoneState.Idle)); Assert.Single(builder2.Build().GetAllowedTransitions(phone, from: PhoneState.OnHold));
} }
} }