mirror of
https://github.com/ditkrg/DIT.Workflower.git
synced 2026-01-22 13:56:43 +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 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>
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user