Refactoring a Complex Order Process with a Three‑Layer Interface and Strategy Template

This article analyzes the problems of a scattered, poorly‑designed order‑processing codebase, proposes a three‑layer interface combined with a strategy‑template pattern, walks through concrete Java implementations, testing and gray‑release safeguards, and shows how the redesign improves readability, maintainability, and extensibility.

Architect
Architect
Architect
Refactoring a Complex Order Process with a Three‑Layer Interface and Strategy Template

1 Background

The original order system suffered from three main issues: core code scattered across multiple services and classes, lack of design patterns leading to heavy if‑else logic, and low delivery efficiency because changes in one order type often impacted others.

Core code was distributed among atomic, service, and MQ‑processing services, with order‑related interfaces hidden in unrelated classes.

Without design patterns, each order type’s logic was tightly coupled, making the code hard to read, extend, and maintain.

Developers had to touch 4‑5 repositories for a single feature, and regression testing was required for all order modes.

When business stabilized, the team decided to refactor the order flow to achieve better readability, maintainability, and extensibility . The key questions were: which design pattern to use, how to test and release the refactored flow, and how to handle incidents.

2 How to Refactor

2.1 Determining the Solution

The game account trading process originally had two basic transaction modes (customer‑service delivery and self‑delivery) that later expanded into seven order types. A naive strategy‑template approach (one abstract template plus seven subclasses) would make the parent class overly bulky.

After analysing the business and code, the team identified three facts:

All order flows derive from the two basic delivery modes.

Every flow contains the same core nodes: place order, pay, upload credentials, deliver, confirm receipt.

Specific order types only add extra steps that are not part of the main flow.

Therefore the team split the process into two layers: a stable core flow (layer 2) and a variable special‑handling layer (layer 3). The top‑level layer (layer 1) aggregates user‑facing, MQ, and inter‑service calls. This resulted in the three‑layer interface + strategy‑template design.

2.2 Three‑Layer Interface + Strategy Template

The interface diagram (see image) shows:

First layer : interfaces used by front‑end, MQ consumers, and other services.

Second layer : core order‑process capabilities (place order, pay, deliver, confirm receipt). Two implementations exist for self‑delivery and customer‑service delivery.

Third layer : special handling for each order type, each with its own implementation class.

public interface IGameAccountOrderDealProcess {
    /** Process an order that has not been paid */
    int handlePlaceOrder(GameAccountOrderContext orderContext) throws Exception;
    /** Process a successfully paid order */
    int handlePaySuccessOrder(GameAccountOrderContext orderContext) throws Exception;
    /** Process a delivered order */
    int handleDeliverOrder(GameAccountOrderContext orderContext) throws Exception;
    /** Process an order cancelled before payment */
    int handleCancelBeforePayOrder(GameAccountOrderContext orderContext) throws Exception;
    /** Process an order cancelled after payment */
    int handleCancelAfterPayOrder(GameAccountOrderContext orderContext) throws Exception;
    /** Process a confirmed receipt order */
    int handleConfirmReceiptOrder(GameAccountOrderContext orderContext) throws Exception;
    <T extends TradeFlowData> T getOrderTradeData(String logStr, Long orderId, Integer device, Long uid);
    ZZOpenScfBaseResult<String> uploadAccountAndPwd(GameAccountSelfTrade.AccountPwdArg arg, long uid, String logStr, ServiceHeaderEntity header) throws Exception;
    boolean deliverOrder(GameAccountOrderContext orderContext) throws Exception;
    ZZOpenScfBaseResult<String> confirmReceiptOrder(GameAccountOrderContext orderContext, Long uid, boolean needCheckRisk) throws Exception;
}
public interface ITradeSelfHandler {
    GameAccountTradeFlow.GameAccountTradeType getOrderTrade();
    /*--- MQ related handling ---*/
    void fillExtraOrderInfoBeforeInsert(GameAccountOrderResultEntity orderEntity, GameAccountOrderContext orderContext);
    void handleAfterPlaceOrder(GameAccountOrderContext orderContext);
    void handleCancelBeforePay(GameAccountOrderContext orderContext);
    int handleCancelAfterPay(GameAccountOrderContext orderContext) throws Exception;
    int handleAfterPaySuccess(GameAccountOrderContext orderContext);
    int handleAfterConfirmReceipt(GameAccountOrderContext orderContext) throws Exception;
    Date getWithDrawlTime();
    void orderAlreadyPayPushMsgNew(GameAccountOrderContext orderContext, Pair<String, String> jumpUrl);
    List<AccountOrderSplitModel> getOrderSplitModelList(GameAccountOrderContext orderContext, OrderMaxSettleInfo settleInfo);
    void buildOrderSpiUiData(GameAccountOrderContext orderContext, GameOrderSpiConfig bConfig, GameOrderSpiConfig sConfig, SpiUiData spiUiData) throws Exception;
    void otherOperationAfterReceipt(GameAccountOrderContext orderContext, Long uid);
}

2.3 Concrete Implementation

Code consolidation : All order‑related logic was moved into a single service, and duplicated code in the admin console, scheduled tasks, and MQ clusters was removed.

Package re‑organization : Interfaces and utilities were placed under one package for easier navigation (see images).

Design principles :

Clear naming – class, variable, and method names convey intent.

Single responsibility – each module handles its own concern, reducing coupling.

Context preparation – a unified GameAccountOrderContext eliminates repeated RPC calls.

public class GameAccountOrderContext {
    private String logStr;
    private Long orderId;
    private Integer mqStatus;
    private Order order;
    private GameAccountOrderResultEntity accountOrderEntity;
    private AccountOrderStatusEnum orderStatus;
    private Boolean hasInsuranceService; // whether the order includes insurance
    private GameAccountTradeFlow.GameAccountTradeType tradeType;
    private GameAccountProductData accountProductData;
    private ZZProduct product;
    private ZZProductExt productExt;
    private Map<String, String> extValueMap;
    private AccountHelpSaleClue helpSaleClue; // sales clue
    private DistributionShareInfoDTO distributionShareInfo; // distribution info
    private ITradeSelfHandler tradeSelfHandler;
    private Integer serviceUiStatus; // order SPI status
}
// Context creation
GameAccountOrderContext orderContext = orderContextBuilder.buildAccountOrderContext(order, zzProduct, logStr);

3 Deployment Safeguards

Because order processing is critical, the team defined two safety goals: accurate offline testing and minimal impact when issues arise online.

3.1 Process Testing

Ensure the order flow runs end‑to‑end.

Validate correct order split accounting.

Confirm insurance handling works.

Check that each node matches the legacy behavior.

Verify push notifications and private messages are sent.

Confirm log statistics are printed correctly.

For each order type, both new and old flows were exercised side‑by‑side, comparing UI pages, buttons, redirects, pushes, and private messages.

3.2 Gray‑Release Strategy

The release uses a gray‑release configuration that limits exposure by order type and daily volume. The configuration is stored as JSON (see snippet) and the decision logic is implemented in isNewOrderProcess:

public boolean isNewOrderProcess(String logStr, GameAccountOrderContext orderContext) {
    Long orderId = orderContext.getOrderId();
    try {
        if (gameGrayTestService.isNewTradeProcessOrder(orderId)) {
            return true;
        }
        GameAccountOrderResultEntity orderEntity = accountOrderManage.getGameAccountOrderEntity(orderId, logStr);
        GameAccountTradeFlow.GameAccountTradeType orderTradeType = orderContext.getTradeType();
        String orderRedisSet = String.format("account_order_gray_set_%s_%s",
            Objects.nonNull(orderEntity) ? orderEntity.getSelfType() : orderTradeType.getSelfType(),
            DateUtil.format(new Date(), "yyyy-MM-dd"));
        if (ZZGameRedisUtil.sismember(orderRedisSet, orderId.toString())) {
            return true;
        }
        if (newAccountOrderTradeSwitch) {
            return true;
        }
        Optional<OrderGrayConfig> grayConfigOptional = grayConfigList.stream()
            .filter(c -> c.getOrderType() == orderTradeType.getSelfType()).findFirst();
        if (grayConfigOptional.isPresent()) {
            OrderGrayConfig grayConfig = grayConfigOptional.get();
            if (Objects.nonNull(grayConfig.getIsTotalGray()) && grayConfig.getIsTotalGray()) {
                return true;
            }
            if (orderContext.getOrderStatus() != AccountOrderStatusEnum.place_order) {
                return false; // only new orders
            }
            String dayNumKey = String.format(NEW_ORDER_PROCESS_GRAY_NUM,
                DateUtil.format(new Date(), "yyyy-MM-dd"), orderTradeType.getSelfType());
            if (NumberUtils.toInt(ZZGameRedisUtil.get(dayNumKey)) < grayConfig.getDayNum()) {
                int result = gameGrayTestService.insertNewTradeProcessOrder(orderId);
                log.info("{} desc=insert_gray_order_data orderId={} result={}", logStr, orderId, result);
                if (result > 0) {
                    ZZGameRedisUtil.increAndGet(dayNumKey, 1);
                    ZZGameRedisUtil.expire(dayNumKey, 3600 * 24);
                    ZZGameRedisUtil.sadd(orderRedisSet, orderId.toString());
                    ZZGameRedisUtil.expire(orderRedisSet, 3600 * 24);
                }
                return result >= 0;
            }
            return false;
        }
    } catch (Exception e) {
        log.error("{} desc=isNewOrderProcess_error orderId={}", orderContext.getLogStr(), orderContext.getOrderId(), e);
    }
    return false;
}

3.3 Exception Mechanism

Critical nodes (credential upload, delivery, withdrawal) trigger enterprise‑WeChat alerts on failure, allowing the team to quickly roll back the gray release. For order split correctness, the team relies on automated tests and suggests integration with a Business Check Platform (BCP) for real‑time data validation.

4 Conclusion

After refactoring, adding or modifying an order type only requires creating or updating the corresponding handler class, without risking impact on other flows. Development efficiency improved dramatically, and the clearer architecture helped the team spot code smells and enhance overall design skills.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsStrategy PatternMicroservicestestingrefactoring
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

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.