mirror of
https://github.com/ditkrg/DIT.Workflower.git
synced 2026-01-22 22:06:42 +00:00
General improvements
This commit is contained in:
parent
d1f669f2c5
commit
98e02edb9c
@ -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>
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
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");
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user