0%

基于Abp的扩展模块实现第3例 - 状态管理(开发)

其他系列博文

背景

通常在各种状态维护业务中,状态切换与处理逻辑会放在一起处理,具有较强耦合性。若使用订阅发布的中介者模式,如MediatR等工具,能减少状态切换与处理动作之间的强耦合。但在一个有限状态机模型中,还有一层源状态与目标状态的对应关系,这层关系很难通过使用MediatR等通用工具直接达到解耦目的,尤其在更为复杂的状态机模型中更是如此。设想一个设备管理系统,要维护一个在线/异常/低电量/休眠/离线/禁用的二维状态模型,存在一对多的切换路径,且需要支持状态递归切换。我们试着建立一个基于Abp的抽象模块来提供针对性的中介者工具。

目标

支持状态切换/处理源状态/目标状态两层解耦,但不假设任何具体业务或持久化方式,以提供足够的抽象。

思路

对于两个解耦层:

  • 源状态/目标状态 - 遵循Abp的Settings等模块风格,实现声明式自注册
  • 状态切换/处理 - 实现类中介者模式的订阅发布功能

另外并支持状态组声明,实现状态层级结构,以应对更复杂的业务模型

编码

首先创建用以声明状态的StateDefinition类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class StateDefinition : IState
{
public string Name { get; }

public StateType Type { get; }

public ILocalizableString DisplayName
{
get => _displayName;
set => _displayName = Check.NotNull(value, nameof(value));
}
private ILocalizableString _displayName;

internal List<StateTransitionDefinition> Transitions { get; } = new List<StateTransitionDefinition>();

public Dictionary<string, object> Properties { get; } = new Dictionary<string, object>();

public object this[string name]
{
get => Properties.GetOrDefault(name);
set => Properties[name] = value;
}

public StateDefinition(
string name,
ILocalizableString displayName = null,
StateType type = StateType.Intermediate)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
DisplayName = displayName ?? new FixedLocalizableString(name);
Type = type;
}

public virtual StateTransitionDefinition On(params string[] terms)
{
Check.NotNullOrEmpty(terms, nameof(terms));
return new StateTransitionDefinition(this, terms.Select(term => Check.NotNullOrWhiteSpace(term, nameof(terms))));
}

public virtual StateDefinition ReverseOn(params string[] terms)
{
Check.NotNullOrEmpty(terms, nameof(terms));
var lastTransition = Transitions.LastOrDefault();
if (lastTransition == null)
{
throw new MyStatesException("No state transition to be reversed.");
}

lastTransition.TargetState.On(terms).TransitTo(this);
return this;
}

public virtual StateDefinition WithProperty(string key, object value)
{
Properties[key] = value;
return this;
}

public virtual bool HasTargetState(StateDefinition targetState)
{
return Transitions.Any(transition => transition.TargetState.Equals(targetState));
}

public override bool Equals(object obj)
{
if (obj == null) return false;
if (ReferenceEquals(this, obj)) return true;
if (!obj.GetType().IsAssignableTo<StateDefinition>()) return false;

return Name == obj.As<StateDefinition>()?.Name;
}

public override int GetHashCode()
{
return Name.GetHashCode();
}
}

博主给这个类封装了一些常用的帮助方法,并支持了本地化。其中最重要的是Transitions属性,它表明了状态与状态之间可能存在的一对多转换关系。转换关系声明类StateTransitionDefinition如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class StateTransitionDefinition
{
public StateDefinition SourceState { get; }

public StateDefinition TargetState { get; protected set; }

public List<string> Terms { get; } = new List<string>();

protected internal StateTransitionDefinition(StateDefinition sourceState, IEnumerable<string> terms)
{
AssertStateAllowTransitFrom(Check.NotNull(sourceState, nameof(sourceState)));

SourceState = sourceState;
Terms = terms.ToList();
}

public virtual StateDefinition TransitTo(StateDefinition targetState)
{
AssertStateAllowTransitTo(Check.NotNull(targetState, nameof(targetState)));

TargetState = targetState;
SourceState.Transitions.Add(this);

return SourceState;
}

protected virtual void AssertStateAllowTransitFrom(StateDefinition sourceState)
{
if (sourceState.Type.HasFlag(StateType.Final))
{
throw new MyStatesException("Final state cannot be transited from.", sourceState.Name);
}
}

protected virtual void AssertStateAllowTransitTo(StateDefinition targetState)
{
if (targetState.Type.HasFlag(StateType.Initial))
{
throw new MyStatesException("Initial state cannot be transited to.", targetState.Name);
}

if (Terms.IsNullOrEmpty())
{
throw new MyStatesException("State transition must have terms.", targetState.Name);
}

if (SourceState.HasTargetState(targetState))
{
throw new MyStatesException("Source state already has a same target state.", targetState.Name);
}

if (SourceState.Transitions
.SelectMany(transition => transition.Terms)
.Intersect(Terms)
.Any())
{
throw new MyStatesException("Source state cannot have multiple transitions with same terms", targetState.Name);
}
}

protected virtual void AssertTransitionAllowReverse()
{
if (SourceState == null || TargetState == null)
{
throw new MyStatesException($"{nameof(SourceState)} and {nameof(TargetState)} should not be null when reversing.");
}

AssertStateAllowTransitFrom(TargetState);
AssertStateAllowTransitTo(SourceState);
}
}

其次,创建状态组声明类StateGroupDefinition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
public class StateGroupDefinition
{
public string Name { get; }

public StateGroupDefinition Parent { get; private set; }

public ILocalizableString DisplayName
{
get => _displayName;
set => _displayName = Check.NotNull(value, nameof(value));
}
private ILocalizableString _displayName;

public IReadOnlyList<StateDefinition> States => _states.ToImmutableList();
private readonly List<StateDefinition> _states = new();

public IReadOnlyList<StateGroupDefinition> Groups => _groups.ToImmutableList();
private readonly List<StateGroupDefinition> _groups = new();

public Dictionary<string, object> Properties { get; } = new Dictionary<string, object>();

public object this[string name]
{
get => Properties.GetOrDefault(name);
set => Properties[name] = value;
}

protected internal StateGroupDefinition(
string name,
ILocalizableString displayName = null,
StateGroupDefinition parent = null)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
DisplayName = displayName ?? new FixedLocalizableString(Name);
Parent = parent;
}

public virtual StateGroupDefinition AddGroup(
string name,
ILocalizableString displayName = null)
{
if (Name == name || _groups.Any(group => group.Name == name))
{
throw new MyStatesException($"There is already an existing state group definition with given name: {name}", stateGroupName: name);
}

var group = new StateGroupDefinition(name, displayName, this);

_groups.Add(group);
return group;
}

public virtual StateDefinition AddState(
string name,
ILocalizableString displayName = null,
StateType type = StateType.Intermediate)
{
var state = new StateDefinition(name, displayName, type);
return AddState(state);
}

public virtual StateDefinition AddState(StateDefinition state)
{
if (_states.Any(s => s.Name == Check.NotNull(state, nameof(state)).Name))
{
throw new MyStatesException($"There is already an existing state definition with given name: {state.Name}", state.Name, Name);
}

_states.Add(state);
return state;
}

public virtual StateGroupDefinition FindGroupRecursively(string name, bool includeSelf = true)
{
Check.NotNullOrWhiteSpace(name, nameof(name));

if (includeSelf && Name == name)
{
return this;
}

foreach (var childGroup in _groups)
{
var group = EnumerateGroupRecursively(childGroup).FirstOrDefault(g => g.Name == name);
if (group != null)
{
return group;
}
}

return null;
}

public virtual List<StateGroupDefinition> GetAllGroups(bool includeSelf = false)
{
var groups = new List<StateGroupDefinition>();

foreach (var group in _groups)
{
AddGroupToListRecursively(groups, group);
}

if (includeSelf)
{
groups.Insert(0, this);
}

return groups;
}

public virtual List<StateDefinition> GetAllStates()
{
var allGroups = GetAllGroups(true);
return allGroups.SelectMany(group => group.States).ToList();
}

public virtual StateTransitionDefinitionCollection On(params string[] terms)
{
Check.NotNullOrEmpty(terms, nameof(terms));
var allStates = GetAllStates();
return new StateTransitionDefinitionCollection(allStates.Select(state => state.On(terms)).ToList());
}

private void AddGroupToListRecursively(List<StateGroupDefinition> groups, StateGroupDefinition group)
{
groups.Add(group);

foreach (var child in group.Groups)
{
AddGroupToListRecursively(groups, child);
}
}

private IEnumerable<StateGroupDefinition> EnumerateGroupRecursively(StateGroupDefinition group)
{
yield return group;

foreach (var innerGroup in group._groups)
{
foreach (var recurseGroup in EnumerateGroupRecursively(innerGroup))
{
yield return recurseGroup;
}
}
}
}

博主按照Abp的代码风格,创建一个StateDefinitionProvider类与一个StateDefinitionManager类来支持使用上面创建的类进行声明配置,及启动时的配置载入:

1
2
3
4
public abstract class StateDefinitionProvider : IStateDefinitionProvider, ITransientDependency
{
public abstract void Define(IStateDefinitionContext context);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class StateDefinitionManager : IStateDefinitionManager, ISingletonDependency
{
protected Lazy<IDictionary<string, StateGroupDefinition>> StateGroupDefinitions { get; }

protected MyStateOptions Options { get; }

protected IServiceProvider ServiceProvider { get; }

public StateDefinitionManager(
IOptions<MyStateOptions> options,
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Options = options.Value;

StateGroupDefinitions = new Lazy<IDictionary<string, StateGroupDefinition>>(CreateStateDefinitions, true);
}

...

protected virtual IDictionary<string, StateGroupDefinition> CreateStateDefinitions()
{
var states = new Dictionary<string, StateGroupDefinition>();

using (var scope = ServiceProvider.CreateScope())
{
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IStateDefinitionProvider)
.ToList();

foreach (var provider in providers)
{
provider.Define(new StateDefinitionContext(states, scope.ServiceProvider));
}
}

return states;
}
}

接下来进行抽象。整个模块中最核心的是两个抽象:

  • IStateTransitionHandler - 放置状态切换处理逻辑,对应状态切换/处理层解耦
  • IStateTransitionNotifier - 放置状态切换决策逻辑,对应源状态/目标状态层解耦
1
2
3
4
public interface IStateTransitionHandler
{
Task HandleAsync(StateTransitionContext context);
}
1
2
3
4
5
6
7
8
public interface IStateTransitionNotifier
{
Task NotifyAsync(
string groupName,
string term,
Action<StateTransitionContext> contextAction = null,
bool recurse = true);
}

对于IStateTransitionHandler我们可完全交由用户代码实现,而对于IStateTransitionNotifier,博主提供了一个默认实现DefaultStateTransitionNotifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class DefaultStateTransitionNotifier : IStateTransitionNotifier, ITransientDependency
{
public ILogger<DefaultStateTransitionNotifier> Logger { get; set; }

protected IServiceProvider ServiceProvider { get; }

protected IStateDefinitionManager DefinationManager { get; }

protected IStateTransitionHandler TransitionHandler { get; }

protected MyStateOptions Options { get; }

public DefaultStateTransitionNotifier(
IServiceProvider serviceProvider,
IStateDefinitionManager definationManager,
IStateTransitionHandler transitionHandler,
IOptions<MyStateOptions> options)
{
ServiceProvider = serviceProvider;
DefinationManager = definationManager;
TransitionHandler = transitionHandler;
Options = options.Value;

Logger = NullLogger<DefaultStateTransitionNotifier>.Instance;
}

public virtual async Task NotifyAsync(
string groupName,
string term,
Action<StateTransitionContext> contextAction = null,
bool recurse = true)
{
Check.NotNullOrWhiteSpace(groupName, nameof(groupName));
Check.NotNullOrWhiteSpace(term, nameof(term));

var group = DefinationManager.GetGroupRecursively(groupName);
var transitions = GetTransitionsToHandle(group, term);
var context = new StateTransitionContext(groupName, term, transitions, recurse);
contextAction?.Invoke(context);

using var scope = ServiceProvider.CreateScope();

var handlers = Options
.TransitionHandlers
.Where(handler =>
{
var handleGroupName = HandleStateAttribute.GetHandleGroupName(handler);
return handleGroupName.IsNullOrEmpty() || DefinationManager.IsGroupBelongTo(groupName, handleGroupName);
})
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IStateTransitionHandler)
.ToList();

foreach (var handler in handlers)
{
await handler.HandleAsync(context);

if (!context.HandlerContext.Any(handlerContext => !handlerContext.Value))
{
break;
}
}

if (!context.HandlerContext.Any(handlerContext => handlerContext.Value))
{
Logger.LogWarning("State transition notification not handled by any of the registered handlers, context: {Context}", context);
}
}

protected virtual IEnumerable<StateTransitionDefinition> GetTransitionsToHandle(StateGroupDefinition group, string term)
{
return group
.GetAllStates()
.SelectMany(state => state.Transitions)
.Where(transition => transition.Terms.Any(t => t == term));
}
}

最后按照惯例创建配置类及模块类,对服务发现及自注册进行支持:

1
2
3
4
5
6
public class MyStateOptions
{
public ITypeList<IStateDefinitionProvider> DefinitionProviders { get; } = new TypeList<IStateDefinitionProvider>();

public ITypeList<IStateTransitionHandler> TransitionHandlers { get; } = new TypeList<IStateTransitionHandler>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[DependsOn(typeof(AbpLocalizationModule))]
public class MyStatesModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
AutoAddDefinitionProviders(context.Services);
}

private static void AutoAddDefinitionProviders(IServiceCollection services)
{
var definitionProviders = new List<Type>();
var transitionHandlers = new List<Type>();

services.OnRegistred(context =>
{
if (typeof(IStateDefinitionProvider).IsAssignableFrom(context.ImplementationType))
{
definitionProviders.Add(context.ImplementationType);
}

if (typeof(IStateTransitionHandler).IsAssignableFrom(context.ImplementationType))
{
transitionHandlers.Add(context.ImplementationType);
}
});

services.Configure<MyStateOptions>(options =>
{
options.DefinitionProviders.AddIfNotContains(definitionProviders);
options.TransitionHandlers.AddIfNotContains(transitionHandlers);
});
}
}

至此,基于Abp的状态管理功能已开发完成。在下一篇博文中,博主会对本文实现的功能编写测试。