Backend Development 30 min read

Design and Implementation of a Scalable Order State Machine for Complex Multi‑Dimensional Order Processing

The article presents a scalable, two‑dimensional order state machine architecture that separates vertical business isolation via annotated StateProcessor classes from horizontal logic reuse through modular checkers and plugins, employing distributed locks, optimistic DB locking, and a registry to ensure extensible, consistent multi‑dimensional order processing.

Amap Tech
Amap Tech
Amap Tech
Design and Implementation of a Scalable Order State Machine for Complex Multi‑Dimensional Order Processing

Background : Order status flow in transaction systems often involves many states, types, scenes, and business dimensions. Maintaining stability while ensuring scalability and maintainability is a core challenge.

Implementation Overview : The solution is split into two dimensions. The vertical dimension addresses business isolation and process orchestration, while the horizontal dimension focuses on logic reuse and business extension.

1. Vertical Solution – State Pattern : Each order state is handled by a separate processor implementing a common StateProcessor interface. Processors are marked with @OrderProcessor to declare the state, business code, and scene they handle.

public interface StateProcessor {
    void action(StateContext context) throws Exception;
}

Example processor annotation:

@OrderProcessor(state = "INIT", bizCode = {"CHEAP", "POPULAR"}, sceneId = "H5")
public class StateCreateProcessor implements StateProcessor {
    // implementation
}

The abstract base class AbstractStateProcessor provides a template method that sequences the workflow: prepare → check → getNextState → action → save → after.

public abstract class AbstractStateProcessor
implements StateProcessor, StateActionStep
{
    @Override
    public final ServiceResult
action(StateContext
context) throws Exception {
        // data preparation
        this.prepare(context);
        // validation
        ServiceResult
result = this.check(context);
        if (!result.isSuccess()) return result;
        // determine next state
        String nextState = this.getNextState(context);
        // business logic
        result = this.action(nextState, context);
        if (!result.isSuccess()) return result;
        // persistence
        result = this.save(nextState, context);
        if (!result.isSuccess()) return result;
        // post‑processing
        this.after(context);
        return result;
    }
}

2. Horizontal Solution – Checkers and Plugins :

• Checkers are small validation components that can be executed serially or in parallel. They are grouped into parameter checkers, synchronous checkers, and asynchronous checkers. The CheckerExecutor runs them and preserves order when needed.

public interface Checker
{
    ServiceResult
check(StateContext
context);
    default int order() { return 0; }
    default boolean needRelease() { return false; }
    default void release(StateContext
context, ServiceResult
result) {}
}

Parallel execution example:

public
ServiceResult
parallelCheck(List
checkers, StateContext
context) {
    if (checkers.size() == 1) return checkers.get(0).check(context);
    // submit each checker to an executor and collect results preserving order
    // ...
}

• Plugins allow insertion of custom logic before or after the main action and save steps. They are annotated with @ProcessorPlugin and implement PluginHandler .

@ProcessorPlugin(state = OrderStateEnum.INIT, event = OrderEventEnum.CREATE, bizCode = "BUSINESS")
public class EstimatePricePlugin implements PluginHandler
{
    @Override
    public ServiceResult action(StateContext
context) throws Exception {
        String price = ""; // call price service
        context.getContext().setEstimatePriceInfo(price);
        return new ServiceResult();
    }
}

The engine executes plugins between the action and save phases:

result = this.action(nextState, context);
if (!result.isSuccess()) return result;
// execute plugins
this.pluginExecutor.parallelExecutor(context);
result = this.save(nextState, context);
if (!result.isSuccess()) return result;

3. Concurrency and Consistency : To prevent concurrent state transitions, the engine acquires a distributed lock (e.g., Redis) per order and uses optimistic locking at the database level. Message consistency between DB updates and event publishing is handled via two‑phase commit (transactional messages) or a reliable outbox table with periodic retry.

4. Registry and Execution Flow : During Spring initialization, all beans annotated with @OrderProcessor are registered in a three‑level map keyed by state → event → bizCode@sceneId. At runtime, OrderFsmEngine.sendEvent builds a StateContext , looks up the appropriate processor, and invokes its template method.

public ServiceResult
sendEvent(OrderStateEvent event, FsmOrder order) throws Exception {
    StateContext context = new StateContext(event, order);
    StateProcessor
processor = registry.acquireStateProcess(order.getOrderState(), event.getEventType(), order.bizCode(), order.sceneId());
    return processor.action(context);
}

The design supports extensibility via inheritance (subclassing processors) or plugin composition, enabling reuse of common logic across many state‑type‑scene combinations.

Backenddesign patternsJavaConcurrencyState Machineorder processingmessage consistency
Amap Tech
Written by

Amap Tech

Official Amap technology account showcasing all of Amap's technical innovations.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.