Backend Development 13 min read

Modular Backend Service Architecture with Automatic Gateway Routing in Spring Cloud

This article explains how to build a highly extensible modular backend service using Spring Cloud Gateway, Gradle scripts, and service‑discovery metadata to automatically generate and refresh routing configurations for both monolithic and microservice deployment modes.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Modular Backend Service Architecture with Automatic Gateway Routing in Spring Cloud

The article introduces a modular backend service design that allows arbitrary combination and extension of functional modules based on project size and requirements. It outlines the following topics: project structure and modular construction ideas, RESTful API design and management, gateway routing modular support, DDD domain‑driven design, RPC modular design with distributed transactions, and event‑driven modular design.

Using a previous article as a reference, three modules ( juejin-user , juejin-pin , juejin-message ) are combined in two ways: (1) merging juejin-user and juejin-message into a single service via the startup module juejin-appliaction-system to reduce resource consumption, and (2) packaging all three modules into a single monolithic application via juejin-appliaction-single for small‑scale projects.

To solve the routing problem caused by frequent module recombination, a dedicated gateway module juejin-gateway is added. The gateway is built with Spring Cloud Gateway (reactive) and requires only the spring-cloud-starter-gateway dependency in build.gradle . Configuration files bootstrap.yml and the gateway’s startup class are provided.

When a conflict between Spring MVC and the reactive gateway occurs, the solution is to set spring.main.web-application-type=reactive or remove the spring-boot-starter-web dependency. Conditional Gradle logic is shown to exclude the web starter for the gateway module:

dependencies {
    if (project.name != "juejin-gateway") {
        implementation 'org.springframework.boot:spring-boot-starter-web'
    }
    // other dependencies...
}

To automate route generation, a processResources task scans project dependencies, collects business module names, writes them to router.properties , and a clean task removes the file. The generated file is then read by a RouterRegister component during service registration, adding the module list to the service instance’s metadata.

@Component
public class RouterRegister {
    @EventListener
    public void register(InstancePreRegisteredEvent event) throws Exception {
        ClassPathResource resource = new ClassPathResource("router.properties");
        Properties properties = new Properties();
        try (InputStream is = resource.getInputStream()) {
            properties.load(is);
        }
        String routers = properties.getProperty("routers");
        Map
metadata = event.getRegistration().getMetadata();
        metadata.put("routers", routers);
    }
}

The gateway implements a custom JuejinRouterDefinitionLocator that listens to HeartbeatEvent , reads the routers metadata from each service instance, and builds RouteDefinition objects dynamically, eliminating manual configuration.

@Component
@RequiredArgsConstructor
public class JuejinRouterDefinitionLocator implements RouteDefinitionLocator {
    private final DiscoveryClient discoveryClient;
    private volatile List
routeDefinitions = Collections.emptyList();

    @Override
    public Flux
getRouteDefinitions() {
        return Flux.fromIterable(routeDefinitions);
    }

    @EventListener
    public void refreshRouters(HeartbeatEvent event) {
        List
newRouteDefinitions = new ArrayList<>();
        List
services = discoveryClient.getServices();
        for (String service : services) {
            List
instances = discoveryClient.getInstances(service);
            if (instances.isEmpty()) continue;
            ServiceInstance instance = instances.get(0);
            String routersMetadata = instance.getMetadata().getOrDefault("routers", "");
            for (String router : routersMetadata.split(",")) {
                RouteDefinition rd = new RouteDefinition();
                rd.setId("router@" + service);
                rd.setUri(URI.create("lb://" + service));
                PredicateDefinition pd = new PredicateDefinition();
                pd.setName("Path");
                pd.addArg("juejin", "/" + router + "/**");
                rd.setPredicates(Collections.singletonList(pd));
                FilterDefinition fd = new FilterDefinition();
                fd.setName("StripPrefix");
                fd.addArg("juejin", "1");
                rd.setFilters(Collections.singletonList(fd));
                newRouteDefinitions.add(rd);
            }
        }
        this.routeDefinitions = newRouteDefinitions;
    }
}

Conditional configuration is introduced to support both monolithic and microservice modes using custom annotations @JuejinBootApplication and @JuejinCloudApplication . The former imports a common configuration ( JuejinBootConfiguration ) and scans the base package while excluding basic components. The latter builds on it and adds cloud‑specific beans such as RouterRegister . By switching the annotation on the main class, developers can easily toggle between single‑application and distributed deployments.

In summary, the article demonstrates how to replace manual routing configuration with automated generation via build tools, metadata registration, and dynamic discovery, while also providing a clean way to manage conditional bean loading for different deployment scenarios.

JavaMicroservicesService DiscoveryGradlegatewaySpring Cloudmodular architecture
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

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.