Skip to content

Commit

Permalink
Merge pull request #19 from EasyAbp/one-way-state
Browse files Browse the repository at this point in the history
Restrict states to have only one-way dependencies
  • Loading branch information
gdlcf88 authored Jul 2, 2024
2 parents 19e71c5 + 97113ea commit 9c2a57f
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,11 @@ public override void ConfigureServices(ServiceConfigurationContext context)
Configure<ProcessManagementOptions>(options =>
{
var definition = new ProcessDefinition("FakeExport", "Fake export")
.AddState(new ProcessStateDefinition("Ready", "Ready"), null)
.AddState(new ProcessStateDefinition("Exporting", "Exporting"), ["Ready", "Exporting"])
.AddState(new ProcessStateDefinition("Failed", "Failed"), ["Ready", "Exporting"])
.AddState(new ProcessStateDefinition("Succeeded", "Succeeded"), "Exporting");
.AddState(new ProcessStateDefinition("Ready", "Ready", null))
.AddState(new ProcessStateDefinition("FailedToStartExporting", "Failed", "Ready"))
.AddState(new ProcessStateDefinition("Exporting", "Exporting", "Ready"))
.AddState(new ProcessStateDefinition("ExportFailed", "Failed", "Exporting"))
.AddState(new ProcessStateDefinition("Succeeded", "Succeeded", "Exporting"));
options.AddOrUpdateProcessDefinition(definition);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Volo.Abp;

namespace EasyAbp.ProcessManagement.Options;
Expand All @@ -19,11 +20,11 @@ public ProcessDefinition(string name, string? displayName)
DisplayName = displayName;
}

public IEnumerable<string> GetChildStateNames(string currentStateName)
public List<string> GetChildrenStateNames(string currentStateName)
{
Check.NotNullOrWhiteSpace(currentStateName, nameof(currentStateName));

return StateDefinitions[currentStateName].NextStateNames;
return StateDefinitions[currentStateName].ChildrenStateNames.ToList();
}

public ProcessStateDefinition GetState(string stateName)
Expand All @@ -33,13 +34,7 @@ public ProcessStateDefinition GetState(string stateName)
return StateDefinitions[stateName];
}

/// <summary>
/// Add a state.
/// </summary>
/// <param name="stateDefinition">The state definition object.</param>
/// <param name="parentStateNames">Names of the parent states. Stages can only transition from their parent states.</param>
/// <returns></returns>
public ProcessDefinition AddState(ProcessStateDefinition stateDefinition, params string[]? parentStateNames)
public ProcessDefinition AddState(ProcessStateDefinition stateDefinition)
{
Check.NotNull(stateDefinition, nameof(stateDefinition));

Expand All @@ -51,32 +46,29 @@ public ProcessDefinition AddState(ProcessStateDefinition stateDefinition, params

StateDefinitions.Add(stateDefinition.Name, stateDefinition);

if (parentStateNames is null)
if (stateDefinition.FatherStateName is null)
{
SetInitialState(stateDefinition.Name);
SetAsInitialState(stateDefinition.Name);
}
else
{
foreach (var parentStateName in parentStateNames)
{
LinkStates(stateDefinition.Name, parentStateName);
}
SetAsChildState(stateDefinition.Name, stateDefinition.FatherStateName);
}

return this;
}

private void LinkStates(string stateName, string parentStateName)
private void SetAsChildState(string stateName, string fatherStateName)
{
Check.NotNullOrWhiteSpace(stateName, nameof(stateName));
Check.NotNullOrWhiteSpace(parentStateName, nameof(parentStateName));
Check.NotNullOrWhiteSpace(fatherStateName, nameof(fatherStateName));

var stateDefinition = StateDefinitions[stateName];

StateDefinitions[parentStateName].NextStateNames.Add(stateDefinition.Name);
StateDefinitions[fatherStateName].ChildrenStateNames.Add(stateDefinition.Name);
}

private void SetInitialState(string stateName)
private void SetAsInitialState(string stateName)
{
Check.NotNullOrWhiteSpace(stateName, nameof(stateName));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,39 @@ namespace EasyAbp.ProcessManagement.Options;

public class ProcessStateDefinition
{
/// <summary>
/// Unique state name.
/// </summary>
public string Name { get; }

/// <summary>
/// Localized display name.
/// todo: use ILocalizableString.
/// </summary>
public string? DisplayName { get; }

internal HashSet<string> NextStateNames { get; } = new();
/// <summary>
/// Name of the father state. Stages can only transition from their father state.
/// If null, this state is the initial state. A process can have only one initial state.
/// </summary>
internal string? FatherStateName { get; }

public ProcessStateDefinition(string name, string? displayName)
/// <summary>
/// Names of the children states. Stages can only transition from their father state.
/// </summary>
internal HashSet<string> ChildrenStateNames { get; } = new();

/// <summary>
///
/// </summary>
/// <param name="name">Localized display name.</param>
/// <param name="displayName">Localized display name.</param>
/// <param name="fatherStateName">Name of the father state. Stages can only transition from their father state.
/// If null, this state is the initial state. A process can have only one initial state.</param>
public ProcessStateDefinition(string name, string? displayName, string? fatherStateName)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
DisplayName = displayName;
FatherStateName = fatherStateName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public virtual Task UpdateStateAsync(Process process, IProcessState nextState)
{
var processDefinition = Options.GetProcessDefinition(process.ProcessName);

var nextStates = processDefinition.GetChildStateNames(process.StateName);
var nextStates = processDefinition.GetChildrenStateNames(process.StateName);

if (!nextStates.Contains(nextState.StateName))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Shouldly;
using Volo.Abp;
using Xunit;

namespace EasyAbp.ProcessManagement;
Expand All @@ -13,32 +14,38 @@ public class ProcessManagementOptionsTests : ProcessManagementDomainTestBase
[Fact]
public void Should_Get_Definitions()
{
var options = ServiceProvider.GetService<IOptions<ProcessManagementOptions>>();

options.ShouldNotBeNull();

var processDefinition = options.Value.GetProcessDefinition("MyDemoProcess");

processDefinition.GetState("Startup").ShouldNotBeNull();
processDefinition.GetState("Step1").ShouldNotBeNull();
processDefinition.GetState("Step2").ShouldNotBeNull();
processDefinition.GetState("Step3").ShouldNotBeNull();
processDefinition.GetState("Step4").ShouldNotBeNull();
processDefinition.GetState("Step5").ShouldNotBeNull();
processDefinition.GetState("Step6").ShouldNotBeNull();
processDefinition.GetState("Step7").ShouldNotBeNull();
processDefinition.GetState("Step8").ShouldNotBeNull();
var options = ServiceProvider.GetRequiredService<IOptions<ProcessManagementOptions>>().Value;

var processDefinition = options.GetProcessDefinition("FakeExport");

processDefinition.GetState("Ready").ShouldNotBeNull();
processDefinition.GetState("FailedToStartExporting").ShouldNotBeNull();
processDefinition.GetState("Exporting").ShouldNotBeNull();
processDefinition.GetState("ExportFailed").ShouldNotBeNull();
processDefinition.GetState("Succeeded").ShouldNotBeNull();
Should.Throw<KeyNotFoundException>(() => processDefinition.GetState("Step10000"));

processDefinition.InitialStateName.ShouldBe("Startup");
processDefinition.GetChildStateNames("Startup").ToArray().ShouldBeEquivalentTo(new[] { "Step1", "Step2" });
processDefinition.GetChildStateNames("Step1").ToArray().ShouldBeEquivalentTo(new[] { "Step3" });
processDefinition.GetChildStateNames("Step2").ToArray().ShouldBeEquivalentTo(new[] { "Step3" });
processDefinition.GetChildStateNames("Step3").ToArray().ShouldBeEquivalentTo(new[] { "Step4", "Step5" });
processDefinition.GetChildStateNames("Step4").ToArray().ShouldBeEquivalentTo(new[] { "Step6" });
processDefinition.GetChildStateNames("Step5").ToArray().ShouldBeEquivalentTo(new[] { "Step7" });
processDefinition.GetChildStateNames("Step6").ToArray().ShouldBeEquivalentTo(new[] { "Step4" });
processDefinition.GetChildStateNames("Step7").ToArray().ShouldBeEquivalentTo(new[] { "Step8" });
processDefinition.GetChildStateNames("Step8").ToArray().ShouldBeEquivalentTo(new[] { "Step5" });
processDefinition.InitialStateName.ShouldBe("Ready");
processDefinition.GetChildrenStateNames("Ready").ToArray()
.ShouldBeEquivalentTo(new[] { "FailedToStartExporting", "Exporting" });
processDefinition.GetChildrenStateNames("Exporting").ToArray()
.ShouldBeEquivalentTo(new[] { "ExportFailed", "Succeeded" });
processDefinition.GetChildrenStateNames("FailedToStartExporting").ToArray().ShouldBeEmpty();
processDefinition.GetChildrenStateNames("Succeeded").ToArray().ShouldBeEmpty();
processDefinition.GetChildrenStateNames("ExportFailed").ToArray().ShouldBeEmpty();
}

[Fact]
public void Should_Not_Add_Duplicate_State()
{
var options = ServiceProvider.GetRequiredService<IOptions<ProcessManagementOptions>>().Value;

var processDefinition = options.GetProcessDefinition("FakeExport");

Should.Throw<AbpException>(() =>
processDefinition.AddState(new ProcessStateDefinition("Ready", "Ready", null)));

Should.Throw<AbpException>(() =>
processDefinition.AddState(new ProcessStateDefinition("Exporting", "Exporting", "Ready")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,20 @@ public override void ConfigureServices(ServiceConfigurationContext context)

private void ConfigureDemoProcessDefinitions(ServiceConfigurationContext context)
{
var processDefinition = new ProcessDefinition("MyDemoProcess", "My Demo Process")
.AddState(new ProcessStateDefinition("Startup", "Startup"))
.AddState(new ProcessStateDefinition("Step1", "Step1"), ["Startup"])
.AddState(new ProcessStateDefinition("Step2", "Step2"), ["Startup"])
.AddState(new ProcessStateDefinition("Step3", "Step3"), ["Step1", "Step2"])
.AddState(new ProcessStateDefinition("Step4", "Step4"), ["Step3", "Step6"])
.AddState(new ProcessStateDefinition("Step5", "Step5"), ["Step3", "Step8"])
.AddState(new ProcessStateDefinition("Step6", "Step6"), ["Step4"])
.AddState(new ProcessStateDefinition("Step7", "Step7"), ["Step5"])
.AddState(new ProcessStateDefinition("Step8", "Step8"), ["Step7"]);

// Step1 Step4 ⇌ Step6
// ↗ ↘ ↗
// Startup Step3
// ↘ ↗ ↘
// Step2 Step5 ➔ Step7
// ↖ ↙
// Step8
/* Succeeded
* ↗
* Exporting
* ↗ ↘
* Ready ExportFailed
* ↘
* FailedToStartExporting
*/
var processDefinition = new ProcessDefinition("FakeExport", "Fake export")
.AddState(new ProcessStateDefinition("Ready", "Ready", null))
.AddState(new ProcessStateDefinition("FailedToStartExporting", "Failed", "Ready"))
.AddState(new ProcessStateDefinition("Exporting", "Exporting", "Ready"))
.AddState(new ProcessStateDefinition("ExportFailed", "Failed", "Exporting"))
.AddState(new ProcessStateDefinition("Succeeded", "Succeeded", "Exporting"));

context.Services.Configure<ProcessManagementOptions>(options =>
{
Expand Down

0 comments on commit 9c2a57f

Please sign in to comment.