Backend Development 5 min read

Refactoring a Multi‑Tenant Backend Service Using DDD and Factory Pattern

This article presents a case study of optimizing a multi‑tenant Java Spring MVC backend by applying domain‑driven design principles, the dependency inversion principle, and a factory pattern to decouple tenant‑specific logic, reduce service size, and improve maintainability and testability.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Refactoring a Multi‑Tenant Backend Service Using DDD and Factory Pattern

1. Background

In a multi‑tenant project that shares a single backend service built with MVC architecture, the code became tightly coupled across tenants, leading to high maintenance cost, bulky services (over 1000 lines), and difficulty in testing and extending functionality.

Tenant‑specific business scenarios are handled with if‑else statements.

Any change for one tenant requires testing all tenants that share the same code.

The service class grew to more than a thousand lines, containing logic for all tenants.

2. Redesign

Following Domain‑Driven Design (DDD), business rules are placed in the domain layer rather than the application layer, allowing each tenant’s logic to be encapsulated within its own domain module, achieving isolation.

Applying the Dependency Inversion Principle, abstractions are defined for common tenant behavior, while concrete implementations reside in tenant‑specific classes.

A factory pattern is introduced to obtain the appropriate tenant object at runtime and delegate business processing accordingly.

3. Refactored Code Example

Controller

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

Here OrderAction is the top‑level interface that defines all tenant business behaviors.

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
orderActionMap = new HashMap<>(2);

    /**
     * Get the corresponding tenant implementation based on tenantId
     */
    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 {
        // Register AOrderAction and BOrderAction implementations
        orderActionMap.put(config.getMap().get(ORDER_TENANT_A), applicationContext.getBean(AOrderAction.class));
        orderActionMap.put(config.getMap().get(ORDER_TENANT_B), applicationContext.getBean(BOrderAction.class));
    }
}

After refactoring, the original 1000‑line service is split into three classes: AbstractOrderAction , AOrderAction , and BOrderAction . The abstract class still holds shared logic (about 500 lines), while each tenant implementation contains its specific behavior, greatly improving decoupling and maintainability.

4. Thoughts and Summary

This design is not suitable for extremely complex or numerous business scenarios, because the top‑level OrderAction interface may become overloaded, leading the abstract class to bloat and re‑creating a large service that needs further splitting.

In conclusion, choose the design that matches the scale of the project; there is no universally best solution, only the most appropriate one for the given context.

Javabackend developmentcode refactoringmulti-tenantDDDSpring MVCFactory Pattern
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.