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 TCommand : struct
{
string Id { get; }
int Version { get; }
string Reference => $"{Id}.v{Version}";
/// <summary>
/// Gets a list of allowed transitions without any condition checks.
/// </summary>

View File

@ -5,8 +5,8 @@ public interface IWorkflowFactory<TState, TCommand, TContext>
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>
where TState : struct
@ -13,16 +12,17 @@ public class DefaultWorkflowFactory<TState, TCommand, TContext> : IWorkflowFacto
_serviceProvider = sp;
}
public IWorkflow<TState, TCommand, TContext> CreateWorkflow()
=> CreateWorkflow(version: 1);
public IWorkflow<TState, TCommand, TContext> CreateWorkflow(string id)
=> 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>>()
.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;
}

View File

@ -5,25 +5,25 @@ namespace DIT.Workflower.DependencyInjection.Extensions;
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 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 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.AddSingleton<IWorkflow<TState, TCommand, TContext>, WorkflowDefinitionWrapper<TState, TCommand, TContext>>(sp =>
{
var definition = ((WorkflowDefinitionBuilder<TState, TCommand, TContext>)builder);
var wrapper = new WorkflowDefinitionWrapper<TState, TCommand, TContext>(definition, version);
var definition = (WorkflowDefinitionBuilder<TState, TCommand, TContext>)builder;
var wrapper = new WorkflowDefinitionWrapper<TState, TCommand, TContext>(definition, id, version);
return wrapper;
});

View File

@ -5,11 +5,14 @@ public record WorkflowDefinitionWrapper<TState, TCommand, TContext> : WorkflowDe
where TCommand : struct
{
public string Id { 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)
{
Id = id;
Version = version;
}

View File

@ -1,5 +1,12 @@
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>
where TState : 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 TCommand : struct
{

View File

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

View File

@ -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<PhoneState, PhoneCommand, PhoneCall>(initial: PhoneState.Idle, version: 1)
sc.AddWorkflowDefinition<PhoneState, PhoneCommand, PhoneCall>(id, version: 1)
.From(PhoneState.Idle)
.On(PhoneCommand.IncomingCall)
.To(PhoneState.Ringing)
@ -27,7 +24,8 @@ public class DependencyInjectionTests
.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)
.To(PhoneState.Ringing)
@ -42,9 +40,11 @@ public class DependencyInjectionTests
var sp = sc.BuildServiceProvider();
var workflowFactory = sp.GetService<IWorkflowFactory<PhoneState, PhoneCommand, PhoneCall>>();
var v1 = workflowFactory?.CreateWorkflow();
var v2 = workflowFactory?.CreateWorkflow(version: 2);
var workflowFactory = sp.GetService<IWorkflowFactory<PhoneState, PhoneCommand, PhoneCall>>()!;
var v1 = workflowFactory.CreateWorkflow(id);
var v2 = workflowFactory.CreateWorkflow(id, version: 2);
Assert.NotNull(workflowFactory);
Assert.NotNull(v1);

View File

@ -2,9 +2,9 @@ namespace DIT.Workflower.Tests;
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]
@ -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));
}
}