前言: 本文针对民宿业务下订单系统状态管理提出了一种解决方案:通过有限状态机极大的简化订单状态的迁移处理,同时使订单状态变得可控。此处的状态机指“有限状态机”
1 有限状态机的概念
有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型 摘自 - 维基百科
FSM(有限状态机)可以使用状态图(或状态转移图)来表示。此外可以使用多种类型的状态转移表。下面展示最常见的表示:当前状态(B)和条件(Y)的组合指示出下一个状态(C)。完整的动作信息可以只使用脚注来增加。包括完整动作信息的FSM定义可以使用状态表。
| 当前状态→
条件↓| 状态A | 状态B|状态C|
| ------------- |:-------------:| -----:|
| 条件X | ... |...|...|
| 条件Y | ... | 状态C| ... |
| 条件Z| ...| ...| ... |
2 民宿业务背景
民宿作为新兴的一种住宿业态与酒店的预定机制不同,民宿的房东会根据房客的基本情况决定是否允许房客入住,即房东要确认房客的订单才能成交。
民宿订单系统作为中间平台既要支撑房源售卖方对自营房源的售卖又要支持对供应商房源的售卖;又要同时作为房源提供商将房源进行分销。
另外,为了考虑房客感受,订单系统要支付1)先支付后确认;2) 先确认后支付两种下单模式。
在复杂的业务背景下导致订单的状态比较多且状态的流转复杂,订单状态变化的触发点非常多。
3 民宿业业务下的订单状态
由于考虑到之后民宿业务的扩展(比如可能接入保险等业务),订单设计时采用订单项的方式。OrderHeader表示订单头信息,每个业务存储在OrderItem中作为订单项存在。
针对民宿的业务场景设计出订单的状态如下:
主订单(OrderHeader)状态枚举
- 处理中
- 成功
- 失败
- 订单取消
- 订单部分取消
- 订单完成
- 订单关闭
房源业务子订单(SpaceOrderItem)枚举
房源订单状态 可以拆分为支付线和业务线来看,前半部分代表支付状态,后半部分代表业务状态。支付状态和业务状态的枚举做笛卡尔积是逻辑上可能存在的所有状态列表。实际中只有下面的状态才是合法状态。
- 待支付待下单
- 待支付下单失败
- 待支付待确认
- 待支付已确认
- 待支付确认失败(拒单)
- 待支付确认失败(超时)
- 待支付已退订
- 已支付待下单
- 已支付待确认
- 已支付已确认
- 待退款下单失败
- 待退款确认失败(拒单)
- 待退款确认失败(超时)
- 待退款已退订
- 待退款部分退订
- 部分退款部分退订
- 部分退款已确认
- 已退款下单失败
- 已退款确认失败(拒单)
- 已退款确认失败(超时)
- 已退款已退订
订单状态的触发场景枚举. 下面的枚举场景是经过抽象的,实际业务场景中远远多于下面的7种
- 创建订单
- 订单支付成功
- 订单退款成功
- 订单被主动拒绝
- 订单被主动接受
- 订单确认超时
- 订单被主动取消
订单状态被触发时除了上面枚举的触发场景,还要结合当时订单的具体形态,比如是否为分销订单、是否为询单模式等综合判断。影响订单状态判断的条件枚举如下:
- 是否已经支付
- 是否为分销订单
- 是否为自营订单
- 是否为询单模式
4 使用FSM对状态进行优化
考虑到上面枚举的各种订单状态类型以及复杂的订单状态变迁触发节点(上面枚举了7种,实际业务场景中要多得多),如果将订单状态的变化分步到具体每个触发的事件中会导致订单状态变化的不可控且极易导致状态变化的混乱。
此外,状态的迁移变化,请自行补脑 if... else ...
4.1 民宿订单状态转移表(部分)
4.2 Java 实现民宿订单FSM(部分,仅做说明使用)
OrderFSM类
封装状态机的三个要素1)状态 2)事件 3)动作
/**
* 订单简明状态机
*
* @author <a href="dongjianxing@aliyun.com">jeff</a>
* @version 2017/1/22 13:37
*/
public class OrderFSM {
private SpaceOrderFSMState fsmSpaceState = SpaceOrderFSMState.NOPAY_NOORDER;
private OrderFSMContextData contextData;
public static OrderFSM init(OrderFSMContextData contextData) {
return new OrderFSM(contextData);
}
public OrderFSM(OrderFSMContextData contextData) {
this.contextData = contextData;
}
public OrderFSM fire(FSMEvent event) throws TongaException {
OrderFSM fsm = null;
switch (event) {
case ORDER_CREATE:
fsm = orderCreate(contextData);
break;
...略...
default:
throw new TongaException("订单FSM不支持的事件类型");
}
return fsm;
}
public SpaceOrderFSMState getFsmSpaceState() {
return fsmSpaceState;
}
public enum SpaceOrderFSMState {
NOPAY_NOORDER(1020, "待支付待下单"),
NOPAY_ORDERFAILED(1023, "待支付下单失败"),
NOPAY_NOCONFIRM(1010, "待支付待确认"),
... 略...
}
public enum FSMEvent {
ORDER_CREATE, //订单创建
BUSINOTIFY_ORDERFAILED,//业务结果通知,下单失败
... 略...
}
//以订单创建为例
private OrderFSM orderCreate(OrderFSMContextData contextData) throws TongaException {
if (fsmSpaceState != SpaceOrderFSMState.NOPAY_NOORDER) {
throw new TongaException(ResultCodeEnum.OTHER_ERROR, "FSM:当前状态不允许 ORDER_CREATE 事件");
}
//分销
if (contextData.isDistribute()) {
if (contextData.isPayed()) {
this.fsmSpaceState = contextData.isSelfSupport() ? SpaceOrderFSMState.PAYED_NOCONFIRM : SpaceOrderFSMState.PAYED_NOORDER;
} else {
this.fsmSpaceState = contextData.isSelfSupport() ? SpaceOrderFSMState.NOPAY_NOCONFIRM : SpaceOrderFSMState.NOPAY_NOORDER;
}
} else {
//非分销
this.fsmSpaceState = contextData.isSelfSupport() ? SpaceOrderFSMState.NOPAY_NOCONFIRM : SpaceOrderFSMState.NOPAY_NOORDER;
}
return this;
}
... 略...
}
OrderFSMContextData存储状态机的上下文信息
用于具体状态变迁时的逻辑判断
/**
* 订单简明状态机上下文数据
*
* @author <a href="dongjianxing@aliyun.com">jeff</a>
* @version 2017/1/22 13:40
*/
public class OrderFSMContextData {
private boolean isPayed;//是否已经支付成功
private boolean isDistribute;//是否为分销
private boolean isSelfSupport;//是否自营
private boolean isInquiry;//是否询单
public OrderFSMContextData(boolean isPayed, boolean isDistribute, boolean isSelfSupport, boolean isInquiry) {
this.isPayed = isPayed;
this.isDistribute = isDistribute;
this.isSelfSupport = isSelfSupport;
this.isInquiry = isInquiry;
}
public boolean isPayed() {
return isPayed;
}
public boolean isDistribute() {
return isDistribute;
}
public boolean isSelfSupport() {
return isSelfSupport;
}
public boolean isInquiry() {
return isInquiry;
}
}
订单状态机使用
//状态机获取子订单状态ID
OrderFSM fsm = OrderFSM.init(
new OrderFSMContextData(支付成功?,
分销订单?,
自营产品?,
询单模式?
)).fire(OrderFSM.FSMEvent.ORDER_CREATE);