用电商订单聊聊状态设计

几乎所有的系统设计都会涉及到状态,需要理清楚有几个状态,状态之间是如何流转的。状态本身设计的好坏,常常对系统的后期演化有着深远的影响。本文试以电商订单为例,聊聊设计状态的时候,需要关注的点有哪些,如何通过系统化思考来尽量保障状态设计的适应性。

背景

电商订单是大家再熟悉不过的场景,从客户提交订单开始,到商品交付结束,再加上期间的用户支付,一个最简单的订单状态机大概会长这样(其中绿色表示终态)。

这个状态,一般会被放在Order表里的State列上。

然后,请仔细思考一下,上面的状态设计是否是合理的。按多数剧本,作为第一个模型,一定是不合理的。

好了,现在假设你已经想好了。无论你觉得这个设计里有哪些问题,先放下答案,再去思考这样一个问题:你是从哪些角度、维度和方向思考的?你为什么会选择那些角度去评估这个设计?你思考这个设计合理性时,所遵循的原则是什么?有没有什么假设?你选择的这些角度、维度、原则,是针对这一个电商场景才需要的?还是对所有类型的状态机设计都是普适的?有没有哪些原则,是所有的状态机设计都需要考虑的?你如何验证自己认为的这些普适原则,真的是普适的。

本文的目的,不是做出一个最合理的订单状态设计案。而是通过电商订单这个例子的状态机设计一步步地演进,同时展示错误的和经过一定思考的不同方案,让大家对于如何做设计,状态设计的原则和标准,形成一些初步的认识。(意思是,你不要有,看一篇文章就可以掌握一个思维模式或方法论的期望,即使有一定概率。)

问题

回到上面的状态机设计上,我们结合现实场景,再思考一下,上面的状态机,是否能应对如下几个问题:

  • 如何支持货到付款?
  • 如何支持更多的支付和快递相关状态?比如,支付中和投递中。
  • 如何支持一个订单,多个快递?

我们一个个地处理一下。

方案

第一个问题,支持货到付款,本质上,是把Paid放在Delivered之后。显然,支付货到付款不是“全面切换”货到付款的意思。所以,两种情况会并存。于是,我们顺理成章地得到如下新的状态机。

有问题吗?好像不太对劲。比如终态怎么还能迁移呢?讲道理哈,针对每一个具体的订单,只有一个固定的流程。要么是走蓝色线,要么是走紫色线。所以并不存在这个“终态再迁移”的问题。

第二个问题,支持更多的状态。加就是了啊。这有什么难的?只是可能要分别处理一下要不要支持货到付款的情况。我们有点儿追求,两个方案都做。

至于到底要用哪个方案,产品来拍个板就好,到底要不要支持货到付款,是个非常重要的,影响整体设计的关键决策呢!

最后一个问题,如何支持多派送单?既然都有了多个派送单了,拆表是肯定要拆了,但是还有一个问题,就是如何在订单状态上体现出一部分派送了一部分没派送这个情况。只是现在情况有点儿复杂,要考虑前面两个问题的答案,才能知道这个如何做。所有的方案排列组合一共有4种,就不一一列出了,只列一个最简单的吧。如下图所示。

补充一下,其中Partially Paid和Partially Delivered都是可选状态,如果一起到的话呢,是可以直接到Paid和Delivered状态的。这是多么灵活,多么人性化的设计啊。

以上,所有问题都解决了。


以上方案都是扯淡。这是停止扯淡的分隔线。


解题

状态机设计,最核心的问题是:先搞清楚是在设计什么东西的状态

想一想,我们是在设计订单的状态吗?可以想象自己是一名客服。接到了客户的电话,这么讲:

你好,我的订单号是1234567890,我能问下这个订单现在是什么状态吗?怎么卡住了?

你有大体两种响应方式。一、把这个订单的所有细节,包括多个物流及支付单都过一下,看看哪里卡住了,原因是什么。二、先问客户确认一下,是哪部分,出了问题。

所以你看,订单其实是多个不同的东西的聚合体,里面的每个部分都是独立的,都有自己的状态。而且状态的变化相对独立。

那么问题来了,订单里的各个服务,又如何划分呢?我按前后端分行不行?当然不行了。这里便引出了,判断模块、组件划分合理性的基本原则:划出的各个模块之间是否正交(即,改变一个模块,而不对其它模块产生影响的性质)。

支付和物流属于订单的一部分,彼此又没有强业务上的依赖关系。那么把他们独立为一个模块,并自己维护自己的状态,会是更合理的选择。从而不难得出如下设计:

这样,三个不同的状态,维护三个不同的对象的状态。每一个对象的状态都是相对稳定的。

我们怎么知道分成这三个状态是不是合理的,是不是足够正交了呢?只要试着,把其中一个对象独立出Order,看各自是否需要做什么改变就好。理想情况下,不用做业务变更,就可以做到下面这样——直接把Delivery拆分成独立服务:

系统状态

于是产品也不需要被迫天天做些莫须有的关键决定——比如,要不要支持货到付款。在这个设计下,根本不是一个重要的问题。当然,处理货到付款的逻辑还是存在的,只是这个决策的影响范围只是在顶层业务逻辑上,而不会影响到底层状态机的设计。

这又引出另一个良好的设计的判定标准。好的架构设计,能让人更晚地去做关键的业务决定(因为好的设计,能在无额外成本的前提下,支持更丰富的业务模式)。良好的设计,从实际场景出发,归纳、整理、抽象出能普适的模型,在解决具体问题的同时,又能维护好通用性。

最后,展示给用户看的状态,可以是这三个不同对象的状态,在不同模式的下的灵活组合。如下图所示:

场景化的用户端状态

注意这里展示给用户的状态的命名,和订单上每个对象上的状态并不需要一样,而且也并不一定要合并成一个状态来展示。这里只是表达这样一个理念:用户状态和系统内部状态分离,以便两个状态都可以独立发生变化

结语

设计的优劣,是决定服务长期可扩展性和可维护性,并持续产生价值的关键因素。而好的设计,需要结合业务实际场景,应用合理的原则,做进一步的分析和建模才能得出;它既不能是原始需求的简单堆砌,也不能是既往经验的原封照搬。

最后,也是最重要的是,是真的能把以上道理用到该用的地方去。如果你发现你没有用上过,或是没有机会用上,那其实只说明你还不是真的懂。

未有知而不行者。知而不行,只是未知。——《传习录》