State Machine Evaluation and Selection for Product Domain
The article analyzes DeWu’s product lifecycle, explains New, Product, and Draft types, introduces state‑machine fundamentals, compares Java enum, Spring, Squirrel, and Cola implementations, benchmarks their throughput (showing Cola vastly faster), and concludes that despite Cola’s performance, Spring StateMachine is chosen for its richer features and seamless Spring integration.
This article presents a comprehensive analysis of the product lifecycle at DeWu, describing three product types—New, Product, and Draft—and their state transitions. Only the Product type is sellable; New items become Products after review, while Drafts are editable copies generated during product updates.
The complex state flow of products (e.g., On‑Shelf, Off‑Shelf, Pending Review, etc.) motivates the introduction of a state machine to manage transitions reliably.
State Machine Fundamentals
A state machine consists of five core components: State, Event, Transition, Guard (condition), and Action. UML 2.4 defines simple, composite, and sub‑machine states, but for most business scenarios a simple state is sufficient.
Enum‑Based State Machine (Java)
public interface StateCondition {
// Check if transition to target state is allowed
boolean check(CommodityState target);
}
public interface StateAction {
void doAction();
}
public enum CommodityState {
TO_AUDIT {
@Override
StateCondition getCondition() {return new ToAuditStateCondition();}
@Override
StateAction getAction() {return new ToAuditStateAction();}
},
ON_SHELF {
@Override
StateCondition getCondition() {return new OnShelfStateCondition();}
@Override
StateAction getAction() {return new OnShelfStateAction();}
},
OFF_SHELF {
@Override
StateCondition getCondition() {return new OffShelfStateCondition();}
@Override
StateAction getAction() {return new OffShelfStateAction();}
};
boolean transition(CommodityState target) {
StateCondition condition = getCondition();
if (condition.check(target)) {
StateAction action = getAction();
action.doAction();
return true;
}
throw new IllegalArgumentException("当前状态不符合流转条件");
}
abstract StateCondition getCondition();
abstract StateAction getAction();
}This enum implementation provides a thread‑safe, readable way to model simple state transitions.
Spring StateMachine
Spring StateMachine offers a full‑featured framework with configuration, persistence, guards, and actions. The core configuration class extends EnumStateMachineConfigurerAdapter and defines states, events, and transitions.
@Configuration
public class SpuStateMachineConfig extends EnumStateMachineConfigurerAdapter
{
public static final String DEFAULT_MACHINEID = "spring/machine/default/machineid";
// ... beans and persister definitions omitted for brevity ...
@Override
public void configure(StateMachineStateConfigurer
config) throws Exception {
config.withStates()
.initial(SpuStatesEnum.NONE)
.states(EnumSet.allOf(SpuStatesEnum.class));
}
@Override
public void configure(StateMachineTransitionConfigurer
transitions) throws Exception {
transitions.withExternal()
.source(SpuStatesEnum.INIT)
.target(SpuStatesEnum.DRAFT)
.event(SpuEventsEnum.CREATE_DRAFT)
.guard(defaultSpuGuard)
.action(spuCreateDraftSuccessAction, defaultSpuErrorAction)
// additional transitions omitted ...
;
}
}The service class SpuStateMachineService creates a Spring context, obtains the factory, and sends events to the machine.
public class SpuStateMachineService {
private final ApplicationContext applicationContext;
private final StateMachineFactory
spuStateMachineFactory;
private final StateMachinePersister
spuStateMachinePersister;
public SpuStateMachineService(String machineId) {
applicationContext = new AnnotationConfigApplicationContext(SpuStateMachineConfig.class);
spuStateMachineFactory = applicationContext.getBean(StateMachineFactory.class);
spuStateMachinePersister = applicationContext.getBean(StateMachinePersister.class);
}
public boolean sendEvent(SpuEventsEnum event, SpuMessageContext context) {
StateMachine
stateMachine =
spuStateMachineFactory.getStateMachine(SpuStateMachineConfig.DEFAULT_MACHINEID);
try {
spuStateMachinePersister.restore(stateMachine, context);
Message
message = MessageBuilder.withPayload(event)
.setHeader("request", context)
.build();
boolean success = stateMachine.sendEvent(message);
if (success) {
spuStateMachinePersister.persist(stateMachine, context);
}
return success;
} finally {
stateMachine.stop();
}
}
}Squirrel StateMachine
Squirrel provides a lightweight DSL‑based builder.
StateMachineBuilder
builder = StateMachineBuilderFactory.create();
builder.externalTransition()
.from(SpuStateEnum.INIT)
.to(SpuStateEnum.DRAFT)
.on(SpuEventEnum.CREATE_DRAFT)
.when(SpuEventEnum.CREATE_DRAFT.getCondition())
.perform(SpuEventEnum.CREATE_DRAFT.getAction());
// other transitions omitted ...Cola StateMachine
Cola focuses on stateless design, using a fluent API to define transitions.
StateMachineBuilder
builder = StateMachineBuilderFactory.create();
builder.externalTransition()
.from(SpuStateEnum.INIT)
.to(SpuStateEnum.DRAFT)
.on(SpuEventEnum.CREATE_DRAFT)
.when(SpuEventEnum.CREATE_DRAFT.getCondition())
.perform(SpuEventEnum.CREATE_DRAFT.getAction());
// additional transitions follow the same patternPerformance Benchmark
JMH benchmarks were written to compare throughput of Spring StateMachine and Cola StateMachine. The benchmark runs with 2 warm‑up iterations, 2 measurement iterations, 8 threads, and 2 forks.
@Warmup(iterations = 2)
@BenchmarkMode({Mode.Throughput})
@Measurement(iterations = 2, time = 1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 2)
@Threads(8)
@State(Scope.Benchmark)
public class SpringStateMachineBench {
private SpuStateMachineService stateMachineService;
@Setup
public void prepare() {
stateMachineService = new SpuStateMachineService("commodity-machine");
}
@Benchmark
public void test_sendEvent() {
SpuMessageContext ctx = new SpuMessageContext("122312", "spu-1222", "https://example.com/video.mp4");
stateMachineService.sendEvent(SpuEventsEnum.CREATE_SPU, ctx);
}
}Result for Spring StateMachine:
Result "SpringStateMachineBench.test_sendEvent":
35.549 ±(99.9%) 27.813 ops/ms [Average]
(min, avg, max) = (31.712, 35.549, 40.171), stdev = 4.304Result for Cola StateMachine:
Result "ColaStateMachineBench.test_sendEvent":
50724.516 ±(99.9%) 31836.478 ops/ms [Average]
(min, avg, max) = (44157.738, 50724.516, 55205.836), stdev = 4926.730The benchmark shows Cola StateMachine achieving roughly 1,449× higher throughput than Spring StateMachine when the actions are empty.
Conclusion
Although Cola offers superior raw performance, the product domain prioritises rich feature support, complex business logic handling, and seamless integration with the Spring ecosystem. Therefore, Spring StateMachine was selected as the final framework for managing product state transitions.
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.