How to Refactor a Multi‑Tenant Service to Reduce Coupling and Code Bloat

The article presents a real‑world case of a multi‑tenant system whose business logic was tightly coupled across tenants, leading to a massive service class, and demonstrates how applying Domain‑Driven Design, the Dependency Inversion Principle, and the Factory pattern can restructure the code into isolated tenant‑specific actions, improving maintainability and testability.

Architect
Architect
Architect
How to Refactor a Multi‑Tenant Service to Reduce Coupling and Code Bloat

Background

The author shares a code‑optimization case of a multi‑tenant project that isolates tenants by a database field and uses a single backend service built on an MVC architecture.

Problems Identified

Business code across tenants is highly coupled, making changes risky.

Testing cost is high because any change requires retesting all tenants.

The service class has grown to over 1,000 lines, containing logic for every tenant, which hampers maintenance and extension.

Redesign Principles

Domain‑Driven Design (DDD): Move business rules to the domain layer, isolating each tenant’s logic in its own domain component.

Dependency Inversion Principle (DIP): Abstract common behavior into interfaces or abstract classes; concrete tenant‑specific details depend on those abstractions.

Factory Pattern: Use a factory to obtain the appropriate tenant‑specific implementation at runtime.

Refactored Code Example

Controller

@PostMapping("/infoByNamePage")
@ApiOperation(value = "分页查询", notes = "分页查询")
public R<Page<OrderInfoPageVO>> infoByNamePage(@Valid @RequestBody OrderInfoVoRequest orderInfoVoRequest) {
    OrderAction orderAction = OrderFactory.getOrderAction(CurrentUserUtil.getTenantId());
    return orderAction.infoByNamePage(orderInfoVoRequest);
}

In this snippet, OrderAction is a top‑level interface that defines all business behaviors for a tenant.

OrderFactory Implementation

public class OrderFactory implements ApplicationContextAware {
    // Tenant A
    private static final String ORDER_TENANT_A = "A";
    // Tenant B
    private static final String ORDER_TENANT_B = "B";

    @Autowired
    private OrderTenantConfig config;

    private static Map<String, OrderAction> orderActionMap = new HashMap<>(2);

    /**
     * Retrieve the OrderAction implementation for the given tenant ID.
     */
    public static OrderAction getOrderAction(String tenantId) {
        OrderAction orderAction = orderActionMap.get(tenantId);
        if (ObjectUtil.isEmpty(orderAction)) {
            throw new OrderBusinessException(OrderCodeEnum.TENANT_ID_IS_NULL);
        }
        return orderAction;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // AOrderAction and BOrderAction are the concrete implementations for each tenant.
        orderActionMap.put(config.getMap().get(ORDER_TENANT_A), applicationContext.getBean(AOrderAction.class));
        orderActionMap.put(config.getMap().get(ORDER_TENANT_B), applicationContext.getBean(BOrderAction.class));
    }
}

Result of Refactoring

After the redesign, the original 1,000‑line service is split into three classes: AbstractOrderAction (≈500 lines) – contains shared logic. AOrderAction – tenant‑A specific implementation. BOrderAction – tenant‑B specific implementation.

This separation decouples tenant code, making the system easier to maintain and extend.

Conclusion

The presented design works well for moderate‑size multi‑tenant projects, but it may become unsuitable when business complexity and the number of tenants grow excessively; in such cases, further decomposition or alternative architectural approaches should be considered.

There is no "best" design, only the most suitable one for the given business size.
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.

Backend ArchitectureCode RefactoringDomain-Driven Designmulti-tenantFactory PatternDependency Inversion
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.