为什么使用这个状态设计模式?
在预约下单模块设计订单状态的时候有7种,用户下单之后变更为待支付,如果取消订单就修改为已取消,如果支付了就修改为派单中,之后服务人员和机构进行抢单或派单修改为待服务、开始服务修改为服务中、订单完成之后修改为已完成。如果有一处用户想取消订单,需要自动退款并将订单状态修改为已关闭。
在上述过程中原本是对订单状态进行硬编码的,如果有一天更改了业务逻辑就需要更改代码,不方便系统扩展和维护,另外订单状态的管理散落在很多地方不方便对订单状态进行统一管理和维护,所以我们使用了状态机来对订单状态进行统一管理
状态设计模式有四个要素:现态、事件、动作、次态
现态:指当前所处的状态
事件:当一个条件被满足,状态会由现态变为新的状态,事件发生会触发一个动作,或者执行一次状态的迁移
动作:发生事件执行的动作,动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态,动作不是必须的,当条件满足后,也可以不执行任何动作,直接迁移到新状态
次态:条件满足后要迁往的新状态
拿支付订单到派单中状态举例:用户下单之后,订单处于待支付状态,用户支付后修改订单状态为派单中
现态:订单当前处于待支付状态那么现态为待支付
事件:用户支付成功为事件,支付成功是条件,当条件满足进行状态迁移
动作:将订单状态由待支付更改为派单中
次态:派单中
实现订单状态机需要的步骤
1.添加需要的组件依赖
组件中包含了以下内容:
核心抽象类:定义了状态机的操作逻辑,如状态初始化、变更及其持久化处理
状态快照:用于记录业务数据的当前状态,方便恢复和追踪
状态事件和处理:定义状态变更事件及其处理逻辑,确保状态转换的正确性
持久化服务:负责状态信息的存储和读取,保存状态机的持久性和一致性
业务快照服务:处理业务数据快照的存储和查询,支持业务的回溯和状态恢复
2.添加订单状态枚举类
订单状态枚举类定义了订单在其生命周期中可能遇到的不同状态
3.添加事件枚举类
事件枚举类定义了订单在处理过程中可能经历的状态变更事件。每个事件由四个部分构成:源状态、目标状态、事件描述和事件名称
4.定义订单快照类
订单快照类是一个数据传输对象(DTO),它扩展了状态机快照的基类,用于表示订单的当前状态快照。订单快照是状态机中一个关键组件,用于记录和追踪订单在其生命周期中的状态变化。这个DTO包括了订单的详细信息,如订单状态、金额、时间戳等,确保可以对订单状态的每次变更进行详细追踪。
此外,OrderSnapshotDTO重写了状态机快照接口中的方法,包括获取和设置快照ID与状态,这使得状态机能够更容易地管理和引用订单状态。通过这种方式,我们可以确保每次状态变更都能被准确记录和恢复,这对于订单处理流程的稳定性和可靠性是非常关键的
5.定义动作类
是订单支付成功的事件处理器,实现了状态变更处理器接口。它主要负责处理订单从“待支付”状态成功转变为“支付成功”状态后的逻辑。当订单支付事件被触发时,该处理器会被调用。
具体来说,处理器中的 handler 方法接收业务ID(即订单ID),状态变更事件以及与订单相关的快照数据。在这个方法中,我们记录处理逻辑开始的日志,并执行与订单状态更新相关的业务操作,如更新订单状态、通知用户支付成功等。
6.定义订单状态机类
订单状态机类是一个特定于订单的状态机,继承自一个抽象的状态机基类,专门用于管理订单状态的变化。订单状态机是系统中关键的一部分,用于确保订单在其生命周期中状态转换的正确性和一致性。
主要功能包括:
构造器:接收状态机持久化程序、业务快照服务和Redis处理程序作为依赖,这些都是管理状态机所需的核心组件。
设置状态机名称:返回状态机的名称为"order",这个名称在系统中用于标识和管理与订单相关的状态变更。
设置初始状态:定义状态机的初始状态为NO_PAY(待支付),这是订单创建后的起始状态。
后处理方法:postProcessor 方法可以在状态变更后可以执行额外的逻辑,比如记录日志、发送通知等。
7.状态机使用MySQL对状态进行持久化
状态机持久化表 (state_persister):
此表用于记录每个业务实体(如订单或服务单)在状态机中的当前状态。
包含字段如state_machine_name(状态机名称,如order或serve),biz_id(业务ID,即订单ID),以及state(当前状态)。
表设计还包括时间戳字段create_time和update_time以追踪记录的创建和更新时间。
使用唯一索引约束state_machine_name和biz_id的组合,确保每个业务ID在其状态机中的唯一性。
状态机快照表 (biz_snapshot):
用于存储业务实体(如订单)在状态变更每个阶段的详细数据快照,以便于查询订单的历史变化。
字段包括state_machine_name,biz_id(与持久化表相同),以及state(快照对应的状态)。
biz_data字段以JSON格式存储订单的详细信息,如金额、状态、时间等。
同样包含create_time和update_time字段,记录快照的创建和更新时间。
db_shard_id字段用于将来可能的数据库分片需求。
通过这两个表,系统能够有效地管理和追踪订单在其生命周期内的状态变更和详细历史
使用订单状态机
例如:
1.加载订单状态机
使用@Import注解将订单状态机导入
2.启动状态机
2.1下单时启动状态机
下单后创建一个新订单,使用状态机的启动方法表示用状态机对该订单的状态开始进行管理
并且将状态机的操作放在业务方法中,并与业务逻辑一起在同一个事务中执行,任何状态机的失败都会导致整个事务的回滚,从而防止了状态不一致的发生
2.2支付成功时使用状态机
定义状态变更处理器:
在订单支付成功处理器(OrderPayedHandler)中,我们专注于处理订单从“待支付”状态到“派单中”状态的转变。处理器首先确认支付信息,包括支付状态、时间、服务商交易单号和第三方支付交易单号等。
这些信息被组织在一个OrderSnapshotDTO对象中,该对象代表了订单的当前状态快照。
使用Spring的@Component注解定义处理器,并通过"order_payed"命名,遵循状态机名称_事件名称的命名规则,以确保系统能正确识别和调用处理器。
支付成功状态变更逻辑:
在处理器的handler方法中,根据业务ID(订单ID)和状态变更事件来更新订单状态。
使用ordersService.updateStatus方法来持久化状态变更。如果更新失败,则抛出DbRuntimeException异常,确保事务能回滚并保持数据一致性。
在业务流程中触发状态变更:
在支付成功的业务方法paySuccess中,首先验证订单当前的支付状态是否为“待支付”,确保不对已处理的订单重复操作。
验证支付通知中的第三方支付单号,确保支付信息的完整性。
构建支付相关的订单快照信息,并调用状态机的changeStatus方法来正式触发状态变更。
此方法同样包裹在@Transactional注解中,确保整个支付成功处理过程的事务性,包括订单状态的更新和支付信息的记录。