How to Build a Scalable Multi‑State Order Processing Engine with State‑Machine Patterns
This article explains how to design a robust order‑status flow engine for transaction systems by applying state‑machine and strategy patterns, separating business logic vertically and horizontally, using annotations, context objects, plug‑in extensions, and ensuring scalability, maintainability, and message‑database consistency.
Introduction: Order status flow is the core work of a transaction system. Order systems often have many states, long chains, complex logic, and multiple scenarios, types, and business dimensions. Ensuring stability while achieving scalability and maintainability is a key challenge.
1 Background
Order status flow is the most critical work of a transaction system, featuring many states, long chains, and complex logic, as well as multi‑scenario, multi‑type, and multi‑business‑dimension characteristics. Under the premise of stable status flow, scalability and maintainability must be addressed.
Taking the Gaode ride‑hailing order status as an example, statuses include passenger order, driver acceptance, driver arrival, trip start, trip end, fee confirmation, payment success, order cancellation, order closure, etc. Order types include premium, fast, taxi, and each type may have sub‑types. Business scenarios include airport pickup, corporate rides, inter‑city carpool, etc.
When status, type, scenario, and other dimensions are combined, each combination may have different processing logic, making a massive if‑else structure impossible. This article discusses solutions for handling "multiple states + multiple types + multiple scenarios + multiple dimensions" while keeping the system extensible and maintainable.
2 Implementation方案
To solve the complex order‑status flow, we design from vertical and horizontal dimensions. Vertically we focus on business isolation and process orchestration; horizontally we focus on logic reuse and business extension.
1 Vertical: Business Isolation and Process Orchestration
Application of State Pattern
For multi‑state or multi‑dimension business logic, we use the State or Strategy pattern. The core idea is "divide and conquer": define a base logic interface, let each state or type implement it, and invoke the corresponding implementation based on the current state or type, achieving independent and isolated code.
This not only improves extensibility and maintainability but also serves as a basic means to reduce impact scope, similar to gray‑release or batch deployment.
/**
* 状态机处理器接口
*/
public interface StateProcessor {
/**
* 执行状态迁移的入口
*/
void action(StateContext context) throws Exception;
}For a single state or type the above method works. For the "multiple states + multiple types + multiple scenarios + multiple dimensions" combination, we annotate implementations with @OrderProcessor, specifying state, bizCode, and sceneId, and let the engine select the appropriate processor at runtime.
Because Java enums cannot be inherited, we use String fields for extensible attributes in annotations.
/**
* 状态机处理器注解标识
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Component
public @interface OrderProcessor {
String[] state() default {};
String[] bizCode() default {};
String[] sceneId() default {};
}When multiple dimensions are combined, we treat the combination as an "event" and use event‑driven state transition: a specific event triggers the next state based on current state and context.
2 Horizontal: Logic Reuse and Business Extension
After establishing vertical isolation, we still encounter repeated logic across different types or scenarios (e.g., coupon verification, invoice calculation). We solve this by two plugin‑based approaches:
Standard flow + differential plugins : Define a default processing flow; write plugins for the small differences.
Common plugins for shared logic : Encapsulate common code as plugins that multiple processors can load.
/**
* 插件注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Component
public @interface ProcessorPlugin {
String[] state() default {};
String event();
String[] bizCode() default {};
String[] sceneId() default {};
}3 State‑Machine Engine Execution Process
Initialization Phase : All classes annotated with @OrderProcessor are registered as Spring beans. A BeanPostProcessor builds a three‑level map: state → event → bizCode@sceneId → list of processors.
public class DefaultStateProcessRegistry implements BeanPostProcessor {
private static Map<String, Map<String, Map<String, List<AbstractStateProcessor>>>> stateProcessMap = new ConcurrentHashMap<>();
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractStateProcessor && bean.getClass().isAnnotationPresent(OrderProcessor.class)) {
OrderProcessor annotation = bean.getClass().getAnnotation(OrderProcessor.class);
initProcessMap(annotation.state(), annotation.event(), annotation.bizCode(), annotation.sceneId(), stateProcessMap, (AbstractStateProcessor) bean);
}
return bean;
}
// ... registration logic omitted for brevity ...
}Runtime Phase : The engine receives an OrderStateEvent, builds a StateContext, looks up the appropriate processor from the map, and invokes its action method. The abstract processor defines a template method that executes six steps: prepare → check → getNextState → action → save → after.
public abstract class AbstractStateProcessor<T, C> implements StateProcessor<T>, StateActionStep<T, C> {
@Override
public final ServiceResult<T> action(StateContext<C> context) throws Exception {
// prepare
this.prepare(context);
// check
ServiceResult<T> result = this.check(context);
if (!result.isSuccess()) return result;
// next state
String nextState = this.getNextState(context);
// business logic
result = this.action(nextState, context);
if (!result.isSuccess()) return result;
// persist
result = this.save(nextState, context);
if (!result.isSuccess()) return result;
// after
this.after(context);
return result;
}
}Checkers are split into param, sync, and async groups. Param checkers run first; sync checkers run serially; async checkers run in parallel using a thread pool, preserving order via a sorted list and Futures.
public class CheckerExecutor {
public <T, C> ServiceResult<T> parallelCheck(List<Checker> checkers, StateContext<C> context) {
if (CollectionUtils.isEmpty(checkers)) return new ServiceResult<>();
if (checkers.size() == 1) return checkers.get(0).check(context);
List<Future<ServiceResult>> futures = new ArrayList<>(checkers.size());
checkers.sort(Comparator.comparingInt(Checker::order));
for (Checker c : checkers) {
futures.add(executor.submit(() -> c.check(context)));
}
for (Future<ServiceResult> f : futures) {
ServiceResult sr = f.get();
if (!sr.isSuccess()) return sr;
}
return new ServiceResult<>();
}
}Message consistency with database updates is handled via two‑phase commit, transactional outbox, or compensation mechanisms, ensuring that state changes and downstream messages stay in sync.
4 Summary
The presented state‑machine engine demonstrates a practical approach to handling complex, multi‑dimensional order workflows. By separating concerns vertically (business isolation, process orchestration) and horizontally (logic reuse via plugins or inheritance), the system achieves high scalability, maintainability, and extensibility.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITFLY8 Architecture Home
ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.
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.
