Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Modernizing Consumer Finance Systems: A Strategic Refactoring Approach

Tech May 17 4

Background

Business Restructuring and Integration

As requirements continued to evolve, the consumer installment division underwent significant adjustments, leading to new product directions. In this context, legacy services that had served for years became increasingly unsuitable for future product planning. Facing new business integration and restructuring, there was an urgent need for new architecture and methodologies to support future business operations.

Addressing Technical Debt

Current primary issues include:

  1. Unclear boundaries between code modules, requiring separation through module and service decomposition to define business boundaries.
  2. Lack of hierarchical structure in code implementation, with simplistic design patterns leading to monolithic, lengthy code sections.

Previously, microservices were organized around dimensions like consumer installments and partner installment products. While this approach clarified project responsibilities and was effective for simple requirement-based service separation, it failed to isolate basic functionalities. This resulted in duplicate code maintenance across various scenarios, increasing project maintenance costs.

Impact on Development Efficiency

Even after taking over the project for some time and gaining sufficient understanding, troubleshooting remained time-consuming and labor-intensive. The internal code structure was complex with intertwined call chains, making normal maintenance challenging. From a long-term development efficiency perspective, a new solution was needed to replace the existing architecture.

Inadequate Monitoring System

The online exception detection mechanism lacked sensitivity, and there was no dashboard for critical business metrics. As business developers, maintaining sensitivity to core indicator data is essential.

Refactoring Objectives

  1. Ensure uninterrupted business operations and iterations;
  2. Improve existing code structure design for better extensibility and enhanced development efficiency;
  3. Gradually replace legacy interfaces with new implementations while phasing out old systems.

Design Approach

Research Phase

Before beginning the refactoring, we researched common architectural solutions in the internet consumer finance sector:

While these generic architectural designs weren't perfectly aligned with Zhuanzhuan's specific consumer installment products, we could adopt the principles of layered architecture design. During the code design phase, we focused on decomposing and planning core modules.

Planning

The frontend and backend refactoring were scheduled in two separate iterations. This phased approach effectively distributed and reduced project risks. The first iteration focused on backend major module isolation and redesign. The second iteration addressed product requirements and integrated with the new frontend.

Refactoring with Preservation Strategy

As business developers, we needed to conduct refactoring while ensuring normal product iterations. The preservation strategy proved to be the optimal approach.

During the first iteration, we retained and isolated legacy edge logic, focusing only on refactoring core code and transferring it to the new system. The new system gradually took over legacy logic while providing RPC interfaces to the old system, eventually replacing it. This approach minimized overall risk while accommodating normal requirement iterations.

In the second iteration, after the first phase's successful implementation, the new system operated stably and had the capability to handle new products. The new system began frontend integration and testing, after which the V2 version was officially launched.

Domain Design (Horizontal Decomposition)

  1. Aggregated Business: Encompasses primary consumer installment businesses, each with distinct product requirements. As upper-level business code, it provides aggregated interfaces to frontend and checkout systems.
  2. Basic Services: Provides data support for financial installment services based on user credit data or partner information.
  3. Third-party Integration: Implements logic based on standardized APIs while enabling flexible integration of partner interfaces.

Module Design (Vertical Decomposition)

Based on previous project challenges and consumer installment characteristics, we decomposed the entire process from purchase to bill repayment: users actively fill out application information, submit credit applications and receive credit limits, select products for installment orders, generate repayment plans, and provide card binding and bill payment functions. This simple installment purchasing process guided our approach to extracting common modules with financial service attributes, such as pre-credit limit acquisition, credit utilization, and bill repayment. These functions were isolated, with each consumer installment product maintaining its own aggregation layer without mutual interference.

Design principles: Without altering existing code logic, we applied single responsibility and dependency inversion principles to decompose and merge system modules, clarifying project responsibilities and reducing coupling. We reorganized packages, defining boundaries between them to further minimize code interdependencies.

Code Design

Effective refactoring relies on design patterns. Building on the existing strategy pattern, we decomposed the partner integration and basic service modules using a combination of double-layer template, strategy, and factory patterns. We designed separate interfaces for credit, utilization, and post-loan modules while maintaining standardized API interfaces for partner integration with flexible adaptation capabilities. The credit module example illustrates this approach:

The first layer implements the strategy pattern for basic services; the second layer implements the strategy pattern for partner integration. The main class diagram design includes:

After defining interfaces and implementations, we established dependencies for the partner integration layer while landing core data for orders, credit utilization, and credit applications to provide data support for consumer installments. The credit module implementation example follows:

Basic Service Interface Definition


/**
 * Credit service interface definition
 **/
public interface ICreditService {

    /**
     * Unique ID defined by the capital party
     */
    String getPartnerId();

    /**
     * Application name
     *
     * @return platform identifier
     */
    String getPlatformName();

     /**
     * Retrieve credit result
     *
     * @return credit result
     */
    CreditResponse evaluateCredit(String transactionId, Long userId);
}

Standard Process Abstraction


/**
 * Standard API integration implementation
 *
 **/
public abstract class BaseCreditService implements ICreditService {
 
    /**
     * Standard API integration
     *
     * @return third-party API service
     */
    protected abstract ThirdPartyApiService getThirdPartyApiService();

    @Override
    public PartnerConfig getPartnerConfiguration() {
        return configProvider.getPartnerConfig(getPartnerId());
    }
    
    @Override
    public CreditResponse evaluateCredit(String transactionId, Long userId) {
        CreditEvaluationRequest request = new CreditEvaluationRequest();
        request.setUserId(userId);
        ApiResponse<CreditEvaluationResponse> response = getThirdPartyApiService().evaluateCredit(transactionId, request);
        String creditStatus = StatusConverter.transformApprovalStatus(response.getData());
        return CreditResponse.builder().status(creditStatus).build();
    }
}

/**
 * Partner-specific implementation
 */
@Service
@Slf4j
public class PartnerXCreditService extends BaseCreditService {

    @Resource
    PartnerXApiService partnerXApiService;

    @Override
    public String getPartnerId() {
        return PartnerConfig.PARTNER_X_ID;
    }
    
    @Override
    public String getPlatformName() {
        return PlatformIdentifier.ZHUANZHUAN.getValue();
    }
    
    @Override
    protected ThirdPartyApiService getThirdPartyApiService() {
        return partnerXApiService;
    }
}

Standard API Interface


/**
 * Standard API interface
 *
 */
public interface ThirdPartyApiService {
    /**
     * Get partner ID
     *
     * @return partner ID
     */
    String getPartnerId();
    
    /**
     * Evaluate credit
     */
    ApiResponse<CreditEvaluationResponse> evaluateCredit(String transactionId, CreditEvaluationRequest request);
}

Internal Standard API Implementation


/**
 * Standard API implementation for partners
 *
 */
@Slf4j
public abstract class BaseThirdPartyApiService implements ThirdPartyApiService {
    @Override
    public ApiResponse<CreditEvaluationResponse> evaluateCredit(String transactionId, CreditEvaluationRequest request) {
        // Common encryption/decryption
        return fetchResponseData(transactionId, getPartnerConfig().getCreditEvaluationUrl(), request, CreditEvaluationResponse.class);
    }
}

Partner-specific Implementation


/**
 * Partner X API interface
 **/
public interface PartnerXApiService extends ThirdPartyApiService {
    /**
     * Get partner ID
     *
     * @return partner ID
     */
    String getPartnerId();
    
    /**
     * Evaluate credit
     */
    ApiResponse<PartnerXCreditResponse> evaluateCredit(PartnerXCreditRequest request);
}

/**
 * Partner-specific abstraction
 **/
@Slf4j
public abstract class PartnerXBaseApiService extends BaseThirdPartyApiService implements PartnerXApiService {
    @Override
    public ApiResponse<PartnerXCreditResponse> evaluateCredit(PartnerXCreditRequest request) {
        // Partner-specific encryption/decryption implementation
        return fetchResponseData(transactionId, getPartnerConfig().getCreditEvaluationUrl(), request, PartnerXCreditResponse.class);
    }
}

/**
 * Partner X integration
 *
 */
@Service
@Slf4j
public class PartnerXApiServiceImpl extends PartnerXBaseApiService {

    @Override
    public String getPartnerId() {
        return PartnerConfig.PARTNER_X_ID;
    }

    @Override
    public String getPlatformName() {
        return PlatformIdentifier.ZHUANZHUAN.getValue();
    }
}

Deployment Process

The transition strategy for the old system was crucial. Given the new table structure design, we implemented unidirectional data synchronization, gradually phasing out legacy system data. If rollback was needed during the gray release phase, we first reverted data changes, prioritizing online service stability.

The refactoring process involved two iterations:

Monitoring System

  1. Project refactoring prioritized monitoring. We implemented Zhuanzhuan's alerting mechanism and Prometheus online monitoring, supplemented by a dedicated online dashboard to promptly identify potential issues across all modules.
  2. Logging: A robust system requires appropriate logging, which often serves as the most efficient tool for problem identification.

Summary

Through this technical refactoring, we not only resolved past technical debt issues but also improved service stability, user experience, and product delivery efficiency.

Technical refactoring is not an overnight process, but with determination and persistent effort, success is achievable. As one quote states: "Don't refuse refactoring due to laziness, don't use lack of time as an excuse for procrastination." Refactoring is a continuous process of optimizing code quality and maintainability that requires our constant attention and action.

I believe another value of refactoring is that a well-refactored system often possesses generality and portability. In other words, our refactored system can be quickly reused by peers with minimal modifications. When you achieve this, you become an expert in technical solutions for your industry.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.