1.2 Four concepts
2.1 DSL
A DSL is a tool whose core value is that it provides a means to more clearly communicate the intent of some part of the system.
This clarity is not just an aesthetic pursuit. The easier a piece of code is to understand, the easier it is to find bugs and make changes to the system. Therefore, we encourage meaningful variable names, clear documentation, and clear code structure. For the same reason, we should also encourage the adoption of DSLs.
2.2 Classification of DSL
builder.externalTransition()
.from(States.STATE1)
.to(States.STATE2)
.on(Events.EVENT1)
.when(checkCondition())
.perform(doAction());
2.3.1 内部DSL示例
def s = new StringWriter()
def xml = new MarkupBuilder(s)
xml.html{
head{
title("Hello - DSL")
script(ahref:"https://xxxx.com/vue.js")
meta(author:"marui116")
}
body{
p("JD-ILT-ITMS")
}
}
println s.toString()
<html>
<head>
<title>Hello - DSL</title>
<script ahref='https://xxxx.com/vue.js' />
<meta author='marui116' />
</head>
<body>
<p>JD-ILT-ITMS</p>
</body>
</html>
A helper class for creating XML or HTML markup. The builder supports various 'pretty printed' formats.
Example:
new MarkupBuilder().root {
a( a1:'one' ) {
b { mkp.yield( '3 < 5' ) }
c( a2:'two', 'blah' )
}
}
Will print the following to System.out:
<root>
<a a1='one'>
<b>3 < 5</b>
<c a2='two'>blah</c>
</a>
</root>
2.3.2 外部DSL
2.3.3 DSL & DDD(领域驱动)
3.1 Spring Statemachine
-
Easy to use flat one level state machine for simple use cases.(易于使用的扁平单级状态机,用于简单的使用案例。) -
Hierarchical state machine structure to ease complex state configuration.(分层状态机结构,以简化复杂的状态配置。) -
State machine regions to provide even more complex state configurations.(状态机区域提供更复杂的状态配置。) -
Usage of triggers, transitions, guards and actions.(使用触发器、transitions、guards和actions。) -
Type safe configuration adapter.(应用安全的配置适配器。) -
Builder pattern for easy instantiation for use outside of Spring Application context(用于在Spring Application上下文之外使用的简单实例化的生成器模式) -
Recipes for usual use cases(通常用例的手册) -
Distributed state machine based on a Zookeeper State machine event listeners.(基于Zookeeper的分布式状态机状态机事件监听器。) -
UML Eclipse Papyrus modeling.(UML Eclipse Papyrus 建模) -
Store machine config in a persistent storage.(存储状态机配置到持久层) -
Spring IOC integration to associate beans with a state machine.(Spring IOC集成将bean与状态机关联起来)
3.2 COLA状态机DSL实现
-
State:状态 -
Event:事件,状态由事件触发,引起变化 -
Transition:流转,表示从一个状态到另一个状态 -
External Transition:外部流转,两个不同状态之间的流转 -
Internal Transition:内部流转,同一个状态之间的流转 -
Condition:条件,表示是否允许到达某个状态 -
Action:动作,到达某个状态之后,可以做什么 -
StateMachine:状态机
4.1 Spring状态机示例
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>3.2.0</version>
</dependency>
4.1.1 构造状态机
4j
public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {
/**
* 定义初始节点、结束节点和状态节点
* @param states the {@link StateMachineStateConfigurer}
* @throws Exception
*/
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states.withStates()
.initial("SI")
.end("SF")
.states(new HashSet<String>(Arrays.asList("S1", "S2", "S3")));
}
/**
* 配置状态节点的流向和事件
* @param transitions the {@link StateMachineTransitionConfigurer}
* @throws Exception
*/
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions.withExternal()
.source("SI").target("S1").event("E1").action(initAction())
.and()
.withExternal()
.source("S1").target("S2").event("E2").action(s1Action())
.and()
.withExternal()
.source("S2").target("SF").event("end");
}
/**
* 初始节点到S1
* @return
*/
public Action<String, String> initAction() {
return ctx -> log.info("Init Action -- DO: {}", ctx.getTarget().getId());
}
/**
* S1到S2
* @return
*/
public Action<String, String> s1Action() {
return ctx -> log.info("S1 Action -- DO: {}", ctx.getTarget().getId());
}
}
public class StateMachineListener extends StateMachineListenerAdapter<String, String> {
public void stateChanged(State from, State to) {
log.info("Transitioned from {} to {}", from == null ? "none" : from.getId(), to.getId());
}
}
4.1.3 状态机配置
4j
public class StateMachineConfig implements WebMvcConfigurer {
private StateMachine<String, String> stateMachine;
private StateMachineListener stateMachineListener;
public void init() {
stateMachine.addStateListener(stateMachineListener);
}
}
4.1.4 接口示例
4.1.4.1 获取状态机状态列表
@RequestMapping("info")
public String info() {
return StringUtils.collectionToDelimitedString(
stateMachine.getStates()
.stream()
.map(State::getId)
.collect(Collectors.toList()),
",");
}
4.1.4.2 状态机开启
public String start() {
stateMachine.startReactively().block();
return state();
}
4.1.4.3 事件操作
"event") (
public String event( (name = "event") String event) {
Message<String> message = MessageBuilder.withPayload(event).build();
return stateMachine.sendEvent(Mono.just(message)).blockLast().getMessage().getPayload();
}
4.1.4.4 获取状态机当前状态
@GetMapping("state")
public String state() {
return Mono.defer(() -> Mono.justOrEmpty(stateMachine.getState().getId())).block();
}
4.1.4.5 一次状态转换的控制台输出
: Completed initialization in 0 ms
: Transitioned from none to SI
: Init Action -- DO: S1
: Transitioned from SI to S1
: S1 Action -- DO: S2
: Transitioned from S1 to S2
: Transitioned from S2 to SF
4.2 COLA状态机示例
4.2.1 构造状态机
StateMachineBuilder<TransNeedStatusEnum, TransNeedEventEnum, Context> builder = StateMachineBuilderFactory.create();
// 接单后,运输需求单生成运输规划单
builder.externalTransition()
.from(None)
.to(UN_ASSIGN_CARRIER)
.on(Create_Event)
.when(checkCondition())
.perform(doAction());
// 运输规划单生成调度单,调度单绑定服务商
builder.externalTransition()
.from(UN_ASSIGN_CARRIER)
.to(UN_ASSIGN_CAR)
.on(Assign_Carrier_Event)
.when(checkCondition())
.perform(doAction());
// 服务商分配车辆、司机
builder.externalTransition()
.from(UN_ASSIGN_CAR)
.to(ASSIGNED_CAR)
.on(Assign_Car_Event)
.when(checkCondition())
.perform(doAction());
// 货物揽收
builder.externalTransition()
.from(ASSIGNED_CAR)
.to(PICKUPED)
.on(Trans_Job_Status_Change_Event)
.when(checkCondition())
.perform(doAction());
// 揽收货物更新到运输中
builder.externalTransition()
.from(ASSIGNED_CAR)
.to(IN_TRANSIT)
.on(Trans_Job_Status_Change_Event)
.when(checkCondition())
.perform(doAction());
// 运输中更新到过海关
builder.externalTransition()
.from(IN_TRANSIT)
.to(PASS_CUSTOMS)
.on(Trans_Job_Status_Change_Event)
// 检查是否需要过海关
.when(isTransNeedPassCustoms())
.perform(doAction());
// 妥投
builder.externalTransition()
.from(PASS_CUSTOMS)
.to(ALL_DELIVERIED)
.on(All_Delivery_Event)
.when(checkCondition())
.perform(doAction());
// 车辆揽收、运输、过海关的运输状态,都可以直接更新到妥投
Stream.of(PICKUPED, IN_TRANSIT, PASS_CUSTOMS)
.forEach(status ->
builder.externalTransition()
.from(status)
.to(ALL_DELIVERIED)
.on(Trans_Job_Status_Change_Event)
.when(checkCondition())
.perform(doAction())
);
// 待分配、待派车、已派车可取消
Stream.of(UN_ASSIGN_CARRIER, UN_ASSIGN_CAR, ASSIGNED_CAR)
.forEach(status ->
builder.externalTransition()
.from(status)
.to(CANCELED)
.on(Order_Cancel_Event)
.when(checkCondition())
.perform(doAction())
);
// 妥投、和取消可结束归档
Stream.of(ALL_DELIVERIED, CANCELED)
.forEach(status ->
builder.externalTransition()
.from(status)
.to(FINISH)
.on(Order_Finish)
.when(checkCondition())
.perform(doAction())
);
stateMachine = builder.build("TransNeedStatusMachine");
@startuml
None --> UN_ASSIGN_CARRIER : Create_Event
UN_ASSIGN_CARRIER --> UN_ASSIGN_CAR : Assign_Carrier_Event
UN_ASSIGN_CAR --> ASSIGNED_CAR : Assign_Car_Event
ASSIGNED_CAR --> CANCELED : Order_Cancel_Event
ASSIGNED_CAR --> PICKUPED : Trans_Job_Status_Change_Event
ASSIGNED_CAR --> IN_TRANSIT : Trans_Job_Status_Change_Event
IN_TRANSIT --> PASS_CUSTOMS : Trans_Job_Status_Change_Event
PASS_CUSTOMS --> ALL_DELIVERIED : Trans_Job_Status_Change_Event
PASS_CUSTOMS --> ALL_DELIVERIED : All_Delivery_Event
IN_TRANSIT --> ALL_DELIVERIED : Trans_Job_Status_Change_Event
ALL_DELIVERIED --> FINISH : Order_Finis
UN_ASSIGN_CAR --> CANCELED : Order_Cancel_Event
UN_ASSIGN_CARRIER --> CANCELED : Order_Cancel_Event
PICKUPED --> ALL_DELIVERIED : Trans_Job_Status_Change_Event
CANCELED --> FINISH : Order_Finis
@enduml
4.2.2 状态机事件处理
/**
* 一种是通过Event来进行事件分发,不同Event通过EventBus走不同的事件响应
* 另一种是在构造状态机时,直接配置不同的Action
* @return
*/
private Action<TransNeedStatusEnum, TransNeedEventEnum, Context> doAction() {
log.info("do action");
return (from, to, event, ctx) -> {
log.info(ctx.getUserName()+" is operating trans need bill "+ctx.getTransNeedId()+" from:"+from+" to:"+to+" on:"+event);
if (from != None) {
TransNeed transNeed = ctx.getTransNeed();
transNeed.setStatus(to.name());
transNeed.setUpdateTime(LocalDateTime.now());
transNeedService.update(transNeed);
}
eventBusService.invokeEvent(event, ctx);
};
}
/**
* @author marui116
* @version 1.0.0
* @className TransNeedAssignCarrierEvent
* @description TODO
* @date 2023/3/28 11:08
*/
(event = TransNeedEventEnum.Assign_Carrier_Event)
4j
public class TransNeedAssignCarrierEvent implements EventComponent {
public void invokeEvent(Context context) {
log.info("分配了服务商,给服务商发邮件和短信,让服务商安排");
}
}
/**
* @author marui116
* @version 1.0.0
* @className TransNeedAssignCarEvent
* @description TODO
* @date 2023/3/28 11:05
*/
(event = TransNeedEventEnum.Assign_Car_Event)
4j
public class TransNeedAssignCarEvent implements EventComponent {
public void invokeEvent(Context context) {
log.info("分配了车辆信息,给运单中心发送车辆信息");
}
}
/**
* @author marui116
* @version 1.0.0
* @className EventServiceImpl
* @description TODO
* @date 2023/3/28 10:57
*/
public class EventBusServiceImpl implements EventBusService {
private ApplicationContextUtil applicationContextUtil;
private Map<TransNeedEventEnum, EventComponent> eventComponentMap = new ConcurrentHashMap<>();
private void init() {
ApplicationContext context = applicationContextUtil.getApplicationContext();
Map<String, EventComponent> eventBeanMap = context.getBeansOfType(EventComponent.class);
eventBeanMap.values().forEach(event -> {
if (event.getClass().isAnnotationPresent(EventAnnonation.class)) {
EventAnnonation eventAnnonation = event.getClass().getAnnotation(EventAnnonation.class);
eventComponentMap.put(eventAnnonation.event(), event);
}
});
}
public void invokeEvent(TransNeedEventEnum eventEnum, Context context) {
if (eventComponentMap.containsKey(eventEnum)) {
eventComponentMap.get(eventEnum).invokeEvent(context);
}
}
}
4.2.3 状态机上下文
public class Context {
private String userName;
private Long transNeedId;
private TransNeed transNeed;
}
4.2.4 状态枚举
public enum TransNeedStatusEnum {
/**
* 开始状态
*/
None,
/**
* 待分配陆运服务商
*/
UN_ASSIGN_CARRIER,
/**
* 待分配车辆和司机
*/
UN_ASSIGN_CAR,
/**
* 订单已处理,已安排司机提货
*/
ASSIGNED_CAR,
/**
* 已完成提货
*/
PICKUPED,
/**
* 运输中
*/
IN_TRANSIT,
/**
* 已通过内地海关
*/
PASS_CUSTOMS,
/**
* 您的货物部分妥投部分投递失败
*/
PARTIAL_DELIVERIED,
/**
* 您的货物妥投
*/
ALL_DELIVERIED,
/**
* 您的货物被拒收
*/
ALL_REJECTED,
/**
* 委托订单被取消
*/
CANCELED,
/**
* 单据结束归档
*/
FINISH;
}
4.2.5 事件枚举
public enum TransNeedEventEnum {
// 系统事件
Create_Event,
Normal_Update_Event,
/**
* 分配服务商事件
*/
Assign_Carrier_Event,
/**
* 派车事件
*/
Assign_Car_Event,
// 车辆任务(trans_jbo)执行修改调度单(trans_task)状态的事件
Trans_Job_Status_Change_Event,
// 派送事件
Partial_Delivery_Event,
All_Delivery_Event,
Partial_Reject_Event,
All_Reject_Event,
// 调度单中的任务单取消事件
Order_Cancel_Event,
// 单据结束
Order_Finish;
public boolean isSystemEvent() {
return this == Create_Event ||
this == Normal_Update_Event;
}
}
4.2.6 接口Demo
4.2.6.1 创建需求单
/**
* 接单
* @return
*/
public Context start( String fsNo, String remark) {
Context context = contextService.getContext();
Object newStatus = stateMachine.getStateMachine().fireEvent(TransNeedStatusEnum.None, TransNeedEventEnum.Create_Event, context);
TransNeed transNeed = transNeedService.createTransNeed(fsNo, remark, newStatus.toString());
context.setTransNeed(transNeed);
context.setTransNeedId(transNeed.getId());
return context;
}
4.2.6.2 分配服务商
/**
* 运输规划单生成调度单,调度单绑定服务商
*/
public Context assignCarrier( Long id) {
Context context = contextService.getContext(id);
TransNeedStatusEnum prevStatus = TransNeedStatusEnum.valueOf(context.getTransNeed().getStatus());
stateMachine.getStateMachine().fireEvent(prevStatus, TransNeedEventEnum.Assign_Carrier_Event, context);
return context;
}
4.2.6.3 分配车辆
public Context assignCar( Long id) {
Context context = contextService.getContext(id);
TransNeedStatusEnum prevStatus = TransNeedStatusEnum.valueOf(context.getTransNeed().getStatus());
log.info("trans need id: {}, prev status: {}", id, prevStatus);
stateMachine.getStateMachine().fireEvent(prevStatus, TransNeedEventEnum.Assign_Car_Event, context);
return context;
}
本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
{{o.name}}
{{m.name}}