When Zhuanzhuan Yanxuan order encounters a state machine

Introduction to State Machines

The state machine mentioned here, the full name is deterministic finite state automaton, is also often referred to as finite automata, or FSM for short. In the field of software, it is widely used, such as compilation, regular expression recognition, and game development. The state machine maintains a set of state collections and event collections, which can input specific events, make state flow, and perform corresponding actions.

State Machine Elements

  • state set (states)
  • Event collection (events)
  • Detectors (guards)
  • transitions
  • context

Scope of business system use

In the Internet business system, all business scenarios involving documents with complex states can use state machines.

business system application

In the business system, through the configuration of the state jump diagram and the business logic encapsulation of the state jump, the system can input a specific event (interface request), make a state jump and execute the corresponding business logic. .

Advantages of State Machines

First of all, what kind of code is good code, the most intuitive feeling is that you can understand it at a glance, which is the so-called clear logic. In other words, it is the idea of ​​​​code expression, which is in line with the way most people think about problems. People are naturally disgusted with complex things and like simple things, which determines that it is difficult for people to directly solve complex problems. Complex problems can often be seen as a combination of many simple problems. In the long practice, we have learned the basics and gradually mastered the solutions to simple problems. However, for complex problems, we also need to master how to split complex problems into simple problems, which is the idea of ​​division. For the structure of the code, we still use this idea to think. From the moment the business logic becomes complex, people have been thinking about how to simplify the problem. With the idea of ​​points, a layered architecture has emerged to separate business logic, control logic and data. When the business logic is further complicated, we use the idea of ​​DDD to split the complex business logic into the behaviors of domain objects, and then organize them together in an easy-to-understand way through domain services.

In a business system, the application of a state machine not only means that the components of the state machine are used, but also means the selection of code architecture, which can be called "state machine architecture". Because it can well split our code logic, and connect different modules together through a specific mechanism to complete the function of the system. This is the role of the state machine in guiding the design of the system. It allows our system to maintain the basic principle of logical splitting in long-term iterations.

除了拆分业务逻辑外,它还把控制逻辑和业务逻辑进行了分离。我们上学时都学过这样一个等式,程序=数据结构+算法,后来一位名为Robert Kowalski的大师进一步论证了,算法=逻辑+控制,并提出如果逻辑和控制分离的话,将会让代码更好维护。在业务逻辑复杂的今天,我们本就应该专注于编写业务逻辑,控制逻辑就应该交尽量交给框架去解决。状态机的引入,也就意味着,它帮我们分担了这部分工作。

最后,直观上看,我们的代码基本消除了对状态判断的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, "待支付"),
    PAYED(2"已支付"),
    ...
}
/**
 * 事件定义
 */
public enum EOrderEvent implements IFSMEvent {
    PAY(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<OrderFSMContext> {
    @Override
    public boolean onGuard(OrderFSMContext context, IFSMState targetState) {
        // 检测器逻辑,校验条件
    }

    @Override
    public void onTransition(OrderFSMContext context, IFSMState targetState) {
        // 转换器逻辑,业务逻辑在这里
    }
}

下一步,就是状态机的触发了,也就是输入事件。这一部分逻辑,可以放到分层架构的service层,当然也可以放到facade层,这取决于你如何设计的系统的架构。这里做事件触发时,需要传入一个上下文信息,来告知状态机当前的初态和事件,也可以传入一些自定义的内容,以便业务逻辑执行时使用。

@Service
public 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
@Slf4j
public class OrderFSMTransBlock implements IFSMTransBlock<OrderFSMContext> {

    @Override
    public boolean onTransBlock(OrderFSMContext context) {
        log.info("状态机无法跳转...");
    }
}

可扩展性考虑

如果有一天,转转在售卖严选手机订单的同时,用户只需支付1元钱即可加购一个手机壳,并且在手机退款时,手机壳必须要同时帮用户退款,如何做呢。按照上边的设计思路,应该这样写:

public class ApplyForRefundTransition implements IFSMTransition<OrderFSMContext> {
    @Override
    public void onTransition(OrderFSMContext context, IFSMState targetState) {
        // 处理手机退款逻辑
        ...
        // 处理手机壳退款逻辑
        ...
    }
}

虽然这样写没什么问题,但是这把两个业务流程耦合在一起了,如果明天需要再加个数据线,后天再加个贴膜...代码就会慢慢腐化,逻辑臃肿,架构坍塌。为了解决这个问题,我们可以设计一个注解,来监听严选手机订单的状态机动作。

@Transition(source = EOrderState.PAYED, event = EOrderEvent.APPLY_FOR_REFUND, fsm = "转转严选订单状态机")
public void phoneShellRefund(OrderFSMContext context) {
    // 处理手机壳退款逻辑
}

写在最后

状态机不是什么高级的技术,重点在于让你用另一种思路去理解,去设计系统,以达到我们想要的目的。生活亦是如此,换一种眼观去看待事物,去理解世界,我们能生活的更幸福。(全文完)


> 转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。

> 关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

Guess you like

Origin juejin.im/post/7121678614893953038