Routing and Request Transformation in API Gateways in Spring Cloud Microservices

API gateways play a crucial role in modern microservices architectures by serving as the centralized entry point for client requests. They can handle the routing requests to the appropriate microservices and it can often involve the request transformation to adapt the client requests to the specific requirements of downstream services. In this article, we will explain the concepts of routing and request the transformation within API gateways in the context of the Spring Cloud microservices.

Routing in an API Gateway can involve defining the paths that guide the requests to the correct microservices. Request transformation can include the changes to the requests before forwarding them such as adding headers, changing the request path, or modifying the query parameters.

Routing

API gateways can route the incoming requests to the appropriate microservices based on predefined rules or patterns. This routing can be based on various criteria such as the URL paths, HTTP headers, or request parameters.

Request Transformation

Client can requests need to be transformed before being the forwarded to the downstream microservices. This transformation might be involve the modifying the request headers, body contents or even the request method.

Key Terminologies:

  • API Gateway: The centralized entry point for the client requests in the microservices architecture. It can handles the routing, request transformation, authentication, authorization and the other cross cutting request parameters or the other criteria.
  • Netflix Eureka: The service discovery tool can provided by the Spring Cloud which allows the microservices to the register themselves and discover the other services within the ecosystem. API gateways often use the Eureka for the dynamically routing the requests to available microservices.
  • Filtering: In Context of the API gateways, filtering refers to the process of the intercepting incoming requests or outgoing responses and performing the actions such as authentication, logging, rate limiting or request transformation based on the predefined criteria.
  • Routing Rules: Configurations defined within the API gateway to the determine how incoming the requests should be routed to the appropriate microservices. These rules can typically specify the criteria such as the URL patterns or request the header to match against incoming requests.
  • Service Registration: The process by which the microservices register themselves with the service registry upon the startup. This can allows the API gateway and the other services to the dynamically discover and communicate with the registered microservices.

Implementation of Routing and Request Transformation in API Gateways in Spring Cloud Microservices

Below is the implementation to route and request transformation in API gateways in Spring Cloud Microservices.

Create the Gateway-Service

Step 1: Create a spring project using spring initializer and add the below dependencies.

Dependencies:

  • Spring Web
  • Spring Dev Tools
  • Lombok
  • Spring Cloud Routing

After creating the Spring project, the file structure will be like below.


Step 2: Open application.properties file and rename it to application.yml. Then, add the following YAML code for configuring the server port and API routing and eureka client configuration of the application.

spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: service_a_route
uri: lb://service-a
predicates:
- Path=/service-a/**
filters:
- AddRequestHeader=X-Request-Service, ServiceA
- id: service_b_route
uri: lb://service-b
predicates:
- Path=/service-b/**
filters:
- AddRequestHeader=X-Request-Service, ServiceB
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/


Step 3: Create the RouteConfig Configuration class.

Go to src > org.example.gatewayservice > config > RouteConfig and put the below code.

Java
package org.example.gatewayservice.config;


import org.example.gatewayservice.fliter.RequestFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RouteConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("service_a_route", r -> r.path("/service-a/**")
                        .filters(f -> f.filter(new RequestFilter())
                                .addRequestHeader("X-Request-Service", "ServiceA"))
                        .uri("lb://service-a"))
                .route("service_b_route", r -> r.path("/service-b/**")
                        .filters(f -> f.filter(new RequestFilter())
                                .addRequestHeader("X-Request-Service", "ServiceB"))
                        .uri("lb://service-b"))
                .build();
    }
}

The RouteConfig class in the gateway can configures the routes to the direct traffic to the service-a and service-b based on the URL path. Each service can checks for the custom header added by the gateway.


Step 4: Create the RequestFliter class.

Go to src > org.example.gatewayservice > filter > RequestFliter and put the below code.

Java
package org.example.gatewayservice.fliter;

import org.springframework.cloud.gateway.filter.GatewayFilter;


import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class RequestFilter implements GatewayFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getRequest().mutate().header("X-Gateway-Filter", "RequestFilter").build();
        return chain.filter(exchange);
    }
}


Step 5: Create the CustomPredicate class.

Go to src > org.example.gatewayservice > predicate> CustomPredicate and put the below code.

Java
package org.example.gatewayservice.predicate;


import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.web.server.ServerWebExchange;

import java.util.function.Predicate;

public class CustomPredicate extends AbstractRoutePredicateFactory<CustomPredicate.Config> {

    public static class Config {
        // Configuration properties if any
    }

    public CustomPredicate() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            // Custom predicate logic
            return exchange.getRequest().getHeaders().containsKey("X-Custom-Header");
        };
    }
}


Step 6: Open the main class add @EnableDiscoveryClient for activating the eureka client functionalities of the application.

Go to src > org.example.gatewayservice > GatewayServiceApplication and put the below code.

Java
package org.example.gatewayservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayServiceApplication.class, args);
    }

}


pom.xml:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>gateway-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway-service</name>
    <description>gateway-service</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
    </properties>
    <dependencies>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


Step 7: Run the application

Once the Spring project successfully runs as a Spring application, it will start at port 8080.

The GatewayApplication is the entry point and the RouteConfig class can configures the routes and applies filters for request transformation. The RequestFilter adds the custom header to the each request.

Create the Eureka-Service

Step 1: Create a Spring project using Spring Initializr, and include the following dependencies:

Dependencies:

  • Spring Web
  • Eureka Server
  • Spring Dev Tools
  • Lombok

After creating the Spring project the folder structure will be like below:


Step 2: Open application.properties file and add the below code to configure the server port and Eureka server configurations for the project.

server:
port: 8761

eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
enable-self-preservation: false

spring:
application:
name: eureka-server


Step 3: In the main class, include @EnableEurekaServer annotation to activate the Eureka server functionality of the application.

Java
package org.example.eurekaservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServiceApplication.class, args);
    }

}


pom.xml:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>eureka-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-service</name>
    <description>eureka-service</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


Step 4: Run the application

Once completed the project after that run the application then it will be start at 8761.


Create the example-service-a microservice

Step 1: Create the spring project using spring initializer on creating the project add the below dependencies into the project.

Dependencies:

  • Spring Web
  • Netflix Eureka Server Client
  • Spring Dev Tools
  • Lombok

After the project creation done, it’s structure will be like below image:


Step 2: Open the application.properties file and insert the below code to configure the server port and Eureka client configuration for the project.

server:
port: 8081

spring:
application:
name: service-a

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/


Step 3: Create the ServiceAController class

Go to src > org.example.exampleservicea > ServiceAController and put the below code.

Java
package org.example.exampleservicea;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/service-a")
public class ServiceAController {

    @GetMapping("/hello")
    public String hello(@RequestHeader("X-Request-Service") String service) {
        return "Hello from Service A! Header: " + service;
    }
}


Step 4: In the main class, include @EnableEurekaServer annotation to activate the Eureka server functionality.

Java
package org.example.exampleservicea;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ExampleServiceAApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleServiceAApplication.class, args);
    }

}


pom.xml:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>example-service-a</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>example-service-a</name>
    <description>example-service-a</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Step 5: Once the Spring project is completed and run as a Spring application successfully, it will start at port 8081.


Create the example-service-b microservice

Step 1: Create the spring project using spring initializer and add the below dependencies.

Dependencies:

  • Spring Web
  • Netflix Eureka Server Client
  • Spring Dev Tools
  • Lombok

Once the project is created, the folder structure will look like below:


Step 2: Open the application.properties file and insert the below code to configure the server port and Eureka client configuration for the project.

server:
port: 8082

spring:
application:
name: service-b

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/


Step 3: Create the ServiceBController class.

Go to src > org.example.exampleserviceb > ServiceBController and put the below code.

Java
package org.example.exampleserviceb;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/service-b")
public class ServiceBController {

    @GetMapping("/hello")
    public String hello(@RequestHeader("X-Request-Service") String service) {
        return "Hello from Service B! Header: " + service;
    }
}


Step 4: In the main class, include the @EnableEurekaServer annotation to activate the Eureka server functionality.

Java
package org.example.exampleserviceb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ExampleServiceBApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleServiceBApplication.class, args);
    }

}


pom.xml:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>example-service-b</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>example-service-b</name>
    <description>example-service-b</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Step 5: Once the Spring project is completed and run as a Spring application successfully, it will start at port 8082.


Once runs all the services of the gateway, eurekaservice, service-a, service-b of the Routing and Request Transformation in API Gateways.


Eureka Dashboard:

http://localhost:8761/eureka

Output:

Service A and Service B: These are two sample spring boot services that responds to the requests and each service can checks for the custom header added by the gateway service.


Example-Service-A API Endpoint:

GET http://localhost:8080/service-a/hello

Output:


Example-Service-B API Endpoint:

GET http://localhost:8080/service-b/hello

Output:

Each of the service will respond with the message indicating the header added by the gateway with the Eureka Configured and the gateway will correctly route the requests to the registered services.

Conclusion

By integrating with the Spring Cloud Gateway with Eureka for the service discovery, we can demonstrate effectively route and transform the requests in the microservices architecture. This setup can ensures that the gateway service can dynamically discover and route the requests to the backend services registered with the Eureka server.




Contact Us