找回密码
 会员注册
查看: 35|回复: 0

当转转严选订单遇到状态机

[复制链接]

2

主题

0

回帖

7

积分

新手上路

积分
7
发表于 2024-9-20 00:34:12 | 显示全部楼层 |阅读模式
状态机简介这里所说的状态机,全名为确定性有穷状态自动机,也常被简称为有穷自动机,简写FSM。在软件领域中,被广泛应用,如编译,正则表达式识别,游戏开发。状态机维护一组状态集合,和事件集合,能够对特定的事件输入,作出状态流转,并执行相应的动作。状态机要素状态集合(states)事件集合(events)检测器(guards)转换器(transitions)上下文(context)业务系统使用范围在互联网业务系统中,所有涉及到包含复杂状态的单据的业务场景,都可以使用状态机。业务系统应用在业务系统中,通过对状态跳转图的配置,以及对状态跳转的业务逻辑封装,完成系统对某一个特定的事件输入(接口请求),做出状态的跳转和对应业务逻辑的执行。状态机的优势首先说什么样的代码是好代码,最直观的感受就是,一看就懂,就是所谓的逻辑清晰,换句话说,就是代码表达的思路,符合大多数人对于问题的思考方式。人天生就对复杂的东西感到厌恶,喜欢简单的东西,这就决定了,人很难直接解决复杂的问题。而复杂的问题,往往可以看成是很多简单问题的组合。在漫长的实践中,我们学习基础,慢慢的掌握了对简单问题的解决方案,然而对于复杂问题,我们还需要掌握如何把复杂问题拆分成一个一个简单问题,这也就是分的思想。对于代码的架构,我们依然是使用这个思想来思考的。从业务逻辑开始变得复杂那一刻起,人们就不断在思考如何将问题变得简单,用分的思想,出现了分层架构,将业务逻辑,控制逻辑与数据分离。当业务逻辑进一步复杂时,我们用DDD的思想,将复杂的业务逻辑,拆分到一个个领域对象的行为当中,再通过领域服务用易理解的方式将它们组织在一起。在业务系统中,应用状态机,不仅仅意味着,使用了状态机的组件,还意味着代码架构的选型,可以称之为“状态机架构”。因为它能够很好的拆分了我们的代码逻辑,并通过特定的机制将不同的模块连接在一起,完成系统的功能。这也就是状态机在系统中起到的指导设计的作用。它能让我们的系统,在长久的迭代中,保持基本的逻辑拆分原则。除了拆分业务逻辑外,它还把控制逻辑和业务逻辑进行了分离。我们上学时都学过这样一个等式,程序=数据结构+算法,后来一位名为RobertKowalski的大师进一步论证了,算法=逻辑+控制,并提出如果逻辑和控制分离的话,将会让代码更好维护。在业务逻辑复杂的今天,我们本就应该专注于编写业务逻辑,控制逻辑就应该交尽量交给框架去解决。状态机的引入,也就意味着,它帮我们分担了这部分工作。最后,直观上看,我们的代码基本消除了对状态判断的if...else...代码,这些代码穿插在业务逻辑中,就好比你坐在桌前思考问题时,有一只苍蝇在你眼前晃来晃去。// 随处可见的状态判断if (state == EOrderState.WAIT_PAY) {  ...} else if (state == EOrderState.PAYED) {  ...}// 下边执行业务逻辑...综述,状态机的引入,能够使代码的学习成本降低,也能够使维护成本降低。状态机在转转严选交易的实践理解业务首先,需要绘制状态流转图。要整体理解业务,把单据按业务规则,抽象出一个一个状态,并且明确,什么动作可以促使它从一个状态变成另一个状态。比如,转转严选的订单,用户下单了之后,系统会生成一个订单,此时应该是“待支付”,那么当用户支付后,系统收到了支付消息,也就是收到了“支付”这个事件,就应该从待支付跳转到“已支付”。在这个过程中,我们看到的“状态”,椭圆一个一个画出来,我们提到的“事件”,用文字写在在两个可以流转的“状态”椭圆之间,然后把两个状态按照流转方向用箭头连接。把所有的状态和事件都画好后,状态机的状态跳转图就呈现出来了。配置状态机然后,根据把状态流转图,转换为我们的代码,为了便于理解,我们可以把代码设计的更加贴近自然语言。比如从“待支付”到“已支付”的代码可以是这样写的。StateMachineConf conf = new StateMachineConf();conf.source(EOrderState.WAIT_PAY)    .onEvent(EOrderEvent.PAY)    .target(EOrderState.PAYED)    .transition(userPayTransition)    ...在这一个配置组合的编码之前,我们还需要把每一个状态,每一个事件都放到枚举里定义好。/** * 状态定义 */public enum EOrderState implements IFSMState {    WAIT_PAY(1, "待支付"),    AYED(2,"已支付"),    ...}/** * 事件定义 */public enum EOrderEvent implements IFSMEvent {    AY(1, "用户支付"),    APPLY_FOR_REFUND(2,"申请退款"),    ...}把所有的状态和事件写好后,我们的配置代码是这样的:StateMachineConf conf = new StateMachineConf();conf.source(s1).onEvent(e1).target(s2).transition(t1)    .and().source(s2).onEvent(e2).target(s3).transition(t2)    ...fsm.setName("转转严选订单状态机");fsm.config(conf);业务逻辑状态机的transition(转换器)是用来执行状态跳转时需要做的事情。所以,需要把我们的业务逻辑,写进对应的transition中,如果不需要执行动作,可以定义一个空的transition/** * 编写业务逻辑 */public class BuyerPayTransition implements IFSMTransition {    @Override    public boolean onGuard(OrderFSMContext context, IFSMState targetState) {        // 检测器逻辑,校验条件    }    @Override    public void onTransition(OrderFSMContext context, IFSMState targetState) {        // 转换器逻辑,业务逻辑在这里    }}下一步,就是状态机的触发了,也就是输入事件。这一部分逻辑,可以放到分层架构的service层,当然也可以放到facade层,这取决于你如何设计的系统的架构。这里做事件触发时,需要传入一个上下文信息,来告知状态机当前的初态和事件,也可以传入一些自定义的内容,以便业务逻辑执行时使用。@Servicepublic class OrderService {    @Resource    private StateMachine fsm;    public void userPay(Order order) {        OrderFSMContext context = new OrderFSMContext();        context.setSourceState(order.getState());        context.setEvent(EOrderEvent.PAY);        context.setOrder(order);        fsm.fire(context);    }}异常情况执行以上方法,状态机就会自动帮我们调用BuyerPayTransition中的逻辑。那么如果出现了异常情况会发生什么呢,比如当前订单已经退过款了,但是系统重复收到了一个退款事件,当然不能重复执行一次退款。首先为了防止并发问题,我们修改订单状态时,要使用类似于乐观锁的机制。update order set state=3 where id=xxx and state=2;然后,状态机在选择逻辑时,发现初始状态为“已退款”,事件为“申请退款”,没有可以执行的逻辑分支,这个时候我们可以选择让状态机抛出异常,或者我们定义一个回调,来打印一些友好的信息,或做一些记录。StateMachineConf conf = new StateMachineConf();conf.source(s1).onEvent(e1).target(s2).transition(t1)    .and().source(s2).onEvent(e2).target(s3).transition(t2)    ...fsm.setName("转转严选订单状态机");fsm.config(conf);fsm.setTransBlock(orderTransBlock);  // 这里配置无法跳转时的回调@Component@Slf4jpublic class OrderFSMTransBlock implements IFSMTransBlock {    @Override    public boolean onTransBlock(OrderFSMContext context) {        log.info("状态机无法跳转...");    }}可扩展性考虑如果有一天,转转在售卖严选手机订单的同时,用户只需支付1元钱即可加购一个手机壳,并且在手机退款时,手机壳必须要同时帮用户退款,如何做呢。按照上边的设计思路,应该这样写:public class ApplyForRefundTransition implements IFSMTransition {    @Override    public void onTransition(OrderFSMContext context, IFSMState targetState) {        // 处理手机退款逻辑        ...        // 处理手机壳退款逻辑        ...    }}虽然这样写没什么问题,但是这把两个业务流程耦合在一起了,如果明天需要再加个数据线,后天再加个贴膜...代码就会慢慢腐化,逻辑臃肿,架构坍塌。为了解决这个问题,我们可以设计一个注解,来监听严选手机订单的状态机动作。@Transition(source = EOrderState.PAYED, event = EOrderEvent.APPLY_FOR_REFUND, fsm = "转转严选订单状态机")public void phoneShellRefund(OrderFSMContext context) {    // 处理手机壳退款逻辑}写在最后状态机不是什么高级的技术,重点在于让你用另一种思路去理解,去设计系统,以达到我们想要的目的。生活亦是如此,换一种眼观去看待事物,去理解世界,我们能生活的更幸福。(全文完)关于作者高宏杰,转转C2B业务研发工程师。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 12:22 , Processed in 1.355071 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表