State Machine Integration for New Product Onboarding
The article details how Dewu’s new product onboarding workflow was refactored into a Spring StateMachine‑driven architecture that separates channel‑specific logic via a Strategy‑pattern operation layer, defines clear enum status domains, and uses guards, actions, and choice states to achieve maintainable, extensible, and idempotent state transitions.
This article describes the design and implementation of a state‑machine‑based workflow for the "new product onboarding" process on the Dewu platform.
It first outlines the business flow: a product goes through several review stages (selection, merchant research, data verification) and the corresponding status fields are stored in two tables – the "new product sample" table and the SPU (Standard Product Unit) table.
Key problems that motivate the state‑machine adoption are:
Large number of status values and unclear transition rules make the code hard to understand and maintain.
Transitions are currently hard‑coded, leading to risky or non‑idempotent state changes.
Adding or modifying statuses requires extensive code changes and full‑process regression testing.
The solution selects Spring StateMachine as the framework and introduces a clean separation of concerns using the Strategy pattern.
Enum definitions for the two status domains are shown:
public enum NewProductShowEnum {
DRAFT(0, "草稿"),
CHECKING(1, "选品中"),
UNPUT_ON_SALE_UNPASS(2, "选品不通过"),
UNPUT_ON_SALE_PASSED(3, "商研审核中"),
UNPUT_ON_SALE_PASSED_UNSEND(4, "商品资料待审核"),
UNPUT_ON_SALE_PASSED_UNSEND_NOT_PUT(5, "鉴别不通过"),
UNPUT_ON_SALE_PASSED_SEND(6, "请寄样"),
SEND_PRODUCT(7, "商品已寄样"),
SEND_PASS(8, "寄样鉴别通过"),
SEND_REJECT(9, "寄样鉴别不通过"),
GONDOR_INVALID(10, "作废"),
FINSH_SPU(11, "新品资料审核通过")
} public enum SpuStatusEnum {
OFF_SHELF(0, "下架"),
ON_SHELF(1, "上架"),
TO_APPROVE(2, "待审核"),
APPROVED(3, "审核通过"),
REJECT(4, "审核不通过"),
TO_RISK_APPROVE(8, "待风控审核"),
TO_LEGAL_APPROVE(9, "待法务审核")
}A generic operation interface abstracts all channel‑specific actions:
public interface NspOperate
{
Integer supportApplyType();
String operateCode();
void preProcessRequest(C context);
void verify(C context);
void process(C context);
void persistent(C context);
void post(C context);
}The factory method that retrieves the correct implementation based on channel and operation code:
public NspOperate getNspOperate(Integer applyType, String operateCode) {
String key = buildKey(applyType, operateCode);
NspOperate nspOperate = operateMap.get(key);
if (Objects.isNull(nspOperate)) {
String generalKey = buildKey(-1, operateCode);
nspOperate = operateMap.get(generalKey);
}
AssertUtils.throwIf(Objects.isNull(nspOperate), "NspOperate not found! key = " + key);
return nspOperate;
}The concrete state‑machine configuration (example for the C‑end channel) defines states, transitions, guards and actions:
@Configuration
@EnableStateMachineFactory(name = "newSpuApplyStateMachineFactory")
public class NewSpuApplyStateMachineConfig extends EnumStateMachineConfigurerAdapter
{
// ... bean definitions omitted for brevity ...
@Override
public void configure(StateMachineStateConfigurer
config) throws Exception {
config.withStates()
.initial(NewProductShowEnum.STM_INITIAL)
.state(NewProductShowEnum.CHECKING)
.state(NewProductShowEnum.UNPUT_ON_SALE_UNPASS)
// other states omitted
.states(EnumSet.allOf(NewProductShowEnum.class));
}
@Override
public void configure(StateMachineTransitionConfigurer
transitions) throws Exception {
transitions.withExternal()
.source(NewProductShowEnum.STM_INITIAL)
.target(NewProductShowEnum.CHECKING)
.event(NewSpuApplyStateMachineEventsEnum.NEW_APPLY)
.guard(nspNewApplyGuard)
.action(nspNewApplyAction)
// more transitions omitted for brevity
;
}
}Guard and action implementations delegate to the channel‑specific NspOperate instance. Example of a guard with a fixed target state:
@Component
public class NspNewApplyGuard extends AbstractGuard
{
@Resource
private NewSpuApplyOperateHelper helper;
@Override
protected boolean process(StateContext
context) {
NewSpuApplyContext ctx = getSendEventContext(context).getRequest().getParams();
Integer applyType = ctx.getApplyType();
NspOperate
op = helper.getNspOperate(applyType, NewSpuApplyStateMachineEventsEnum.NEW_APPLY.getCode());
op.preProcessRequest(ctx);
op.verify(ctx);
return true;
}
}And the corresponding action:
@Component
public class NspNewApplyAction extends AbstractSuccessAction
{
@Resource
private NewSpuApplyOperateHelper helper;
@Override
protected void process(StateContext
context) {
NewSpuApplyContext ctx = getSendEventContext(context).getRequest().getParams();
Integer applyType = ctx.getApplyType();
NspOperate
op = helper.getNspOperate(applyType, NewSpuApplyStateMachineEventsEnum.NEW_APPLY.getCode());
op.process(ctx);
op.persistent(ctx);
op.post(ctx);
}
}For transitions that require runtime decision, a Choice state is used together with a NspStatusDecider interface to compute the target status based on business logic.
The article also discusses how the design decouples channel‑specific logic (via strategy beans), keeps guard/action code reusable, and simplifies future extensions such as adding new channels, new status nodes, or reordering states.
In conclusion, the combination of a well‑structured state machine and a strategy‑based operation layer reduces code coupling, improves maintainability, and provides a clear, extensible framework for the product onboarding workflow.
DeWu Technology
A platform for sharing and discussing tech knowledge, guiding you toward the cloud of technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.