通过状态机优化民宿订单系统

前言: 本文针对民宿业务下订单系统状态管理提出了一种解决方案:通过有限状态机极大的简化订单状态的迁移处理,同时使订单状态变得可控。此处的状态机指“有限状态机”

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)枚举
房源订单状态 可以拆分为支付线和业务线来看,前半部分代表支付状态,后半部分代表业务状态。支付状态和业务状态的枚举做笛卡尔积是逻辑上可能存在的所有状态列表。实际中只有下面的状态才是合法状态。

  1. 待支付待下单
  • 待支付下单失败
  • 待支付待确认
  • 待支付已确认
  • 待支付确认失败(拒单)
  • 待支付确认失败(超时)
  • 待支付已退订
  • 已支付待下单
  • 已支付待确认
  • 已支付已确认
  • 待退款下单失败
  • 待退款确认失败(拒单)
  • 待退款确认失败(超时)
  • 待退款已退订
  • 待退款部分退订
  • 部分退款部分退订
  • 部分退款已确认
  • 已退款下单失败
  • 已退款确认失败(拒单)
  • 已退款确认失败(超时)
  • 已退款已退订

订单状态的触发场景枚举. 下面的枚举场景是经过抽象的,实际业务场景中远远多于下面的7种

  1. 创建订单
  • 订单支付成功
  • 订单退款成功
  • 订单被主动拒绝
  • 订单被主动接受
  • 订单确认超时
  • 订单被主动取消

订单状态被触发时除了上面枚举的触发场景,还要结合当时订单的具体形态,比如是否为分销订单、是否为询单模式等综合判断。影响订单状态判断的条件枚举如下:

  1. 是否已经支付
  • 是否为分销订单
  • 是否为自营订单
  • 是否为询单模式

4 使用FSM对状态进行优化

考虑到上面枚举的各种订单状态类型以及复杂的订单状态变迁触发节点(上面枚举了7种,实际业务场景中要多得多),如果将订单状态的变化分步到具体每个触发的事件中会导致订单状态变化的不可控且极易导致状态变化的混乱。
此外,状态的迁移变化,请自行补脑 if... else ...

4.1 民宿订单状态转移表(部分)

状态迁移图.jpg

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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容