Evolving an Order Processing System with Design Patterns: From Chain of Responsibility to Distributed Asynchronous Flow
This article walks through a real‑world order‑processing case, demonstrating how successive design‑pattern refinements—starting with the Chain of Responsibility, then adding Factory, Proxy, Template Method, Observer and distributed messaging—enable multi‑tenant, asynchronous, and scalable backend workflows.
The article presents a step‑by‑step evolution of an order‑processing system, using common design patterns to progressively meet new business requirements while keeping the codebase clean and extensible.
1. Introduction – The goal is to illustrate how typical design patterns can be applied in practice to improve code architecture for a business‑driven order workflow.
2. Background – An order‑flow management system must process a series of steps for each user‑initiated order.
3. First Iteration – Chain of Responsibility – To avoid a long if‑else chain, the responsibility‑chain pattern is introduced. An abstract handler defines the next handler and a handle method.
@Data
public abstract class BizOrderHandler {
private BizOrderHandler nextBizOrderHandler;
public abstract Result handle(ProductVO param);
protected Result next(ProductVO param) {
if (Objects.isNull(nextBizOrderHandler)) {
return Result.success();
}
return nextBizOrderHandler.handle(param);
}
}A concrete handler for storage checking is then implemented.
public class StorageCheckBizOrderHandler extends BizOrderHandler {
@Override
public Result handle(ProductVO param) {
System.out.println("StorageCheckBizOrderHandler doing business!");
return super.next(param);
}
}The OrderHandleCases class builds the chain from a configuration map.
public class OrderHandleCases {
static Map
handlerMap = new HashMap<>();
static {
handlerMap.put("Storage", new StorageCheckBizOrderHandler());
// other handlers omitted for brevity
}
public static void main(String[] args) {
BizOrderHandler handler1 = initHandler(Lists.newArrayList("Storage", "RightCenter", "PointDeduction", "Payment"));
ProductVO productVO = ProductVO.builder().build();
Result result = handler1.handle(productVO);
System.out.println("订单处理 成功");
}
private static BizOrderHandler initHandler(List
handlerNameChain) {
// builds the chain by linking handlers from the map
}
}This first version works but later reveals a stack‑overflow when the same singleton instances are reused for multiple tenants, forming a circular chain.
4. Second Iteration – Multi‑Tenant Support with Simple Factory – To avoid shared singleton instances, a simple‑factory creates a fresh handler for each step.
public class BizOrderHandlerFactory {
public static BizOrderHandler buildBizOrderHandler(String bizType) {
switch (bizType) {
case "Storage": return new StorageCheckBizOrderHandler();
case "Payment": return new PaymentBizOrderHandler();
case "RightCenter": return new RightCenterBizOrderHandler();
case "PointDeduction": return new PointDeductBizOrderHandler();
default: return null;
}
}
}The initialization method now creates new objects and links them, eliminating the circular reference.
private static BizOrderHandler initHandlerPro(List
handlerNameChain) {
List
handlers = new ArrayList<>();
for (String s : handlerNameChain) {
handlers.add(BizOrderHandlerFactory.buildBizOrderHandler(s));
}
// link handlers sequentially
return handlers.get(0);
}Both tenant A and tenant B can now run independently.
5. Third Iteration – Conditional Execution with Proxy (Union Gateway) – A new requirement asks that either the rights‑deduction or point‑deduction succeed before proceeding to payment. A proxy (gateway) handler aggregates the two checks.
@Data
public class BizOrderHandlerUnionGateway extends BizOrderHandler {
List
proxyHandlers;
@Override
public Result handle(ProductVO param) {
boolean isAllSuccess = true;
for (BizOrderHandler handler : proxyHandlers) {
Result result = handler.handle(param);
if (!result.isSuccess()) { isAllSuccess = false; break; }
}
if (isAllSuccess) return super.next(param);
else throw new RuntimeException("execute Failed");
}
}The factory is extended to create this gateway, and the chain is assembled so that the gateway sits between storage checking and payment.
private static BizOrderHandler initHandlerWithGateway() {
BizOrderHandler storage = BizOrderHandlerFactory.buildBizOrderHandler("Storage");
BizOrderHandlerUnionGateway unionGateway = (BizOrderHandlerUnionGateway) BizOrderHandlerFactory.buildBizOrderHandler("UnionGateway");
// configure proxyHandlers with RightCenter and PointDeduction
storage.setNextBizOrderHandler(unionGateway);
unionGateway.setNextBizOrderHandler(BizOrderHandlerFactory.buildBizOrderHandler("Payment"));
return storage;
}6. Fourth Iteration – Asynchronous Distributed Flow (Observer Pattern) – Performance concerns lead to an asynchronous, message‑driven design. An OrderFlowEvent is defined and sent through a message queue. An abstract BizHandler provides a template method that processes the event, forwards it to the next step, and handles retries or failures.
@Data
public class OrderFlowEvent implements Serializable {
private String orderNo;
private String currentFlow;
private String nextFlow;
} public abstract class BizHandler {
String topicName = "biz_handle_topic";
Map
handlerMap = new HashMap<>();
Map
handlerChain = new LinkedHashMap<>();
public void handle(String msg) {
// deserialize, invoke concrete handleEvent, then send next message if needed
}
public abstract Result handleEvent(OrderFlowEvent event);
public void sendFlowMsg(Object data, String currentFlow, String nextFlow) {
// push new OrderFlowEvent to MQ
}
}Concrete handlers such as StorageBizHandler implement handleEvent . The client builds the handler map and chain, then fires the first event.
public class OrderFlowClient {
void handleOrder() {
Map
handlerMap = new HashMap<>();
handlerMap.put("Storage", new StorageBizHandler());
// other handlers omitted
Map
handlerChain = new LinkedHashMap<>();
handlerChain.put("Storage", "PointDeduction");
handlerChain.put("PointDeduction", "Payment");
// start flow by sending first event to MQ
}
}The article concludes that through successive applications of Chain of Responsibility, Factory, Proxy, Template Method, and Observer patterns, the system evolves from a simple monolithic flow to a flexible, multi‑tenant, asynchronous, and distributed architecture.
7. Summary – The case study demonstrates how appropriate design patterns can systematically address evolving business needs, improve code maintainability, and enable scalability in backend systems.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.