Spring Cloud Gateway made simple

Spring Cloud Gateway is a tool provided by Spring Cloud that helps in routing incoming requests to different microservices. It acts as a gateway (or proxy) that routes client requests to various services based on certain conditions. In this tutorial we will continue our Spring Cloud Journey

Why a Spring Cloud Ecosystem Needs Spring Cloud Gateway

In a microservices architecture, Spring Cloud Gateway is essential for managing and routing requests effectively. It provides a centralized entry point, simplifying routing by directing traffic based on dynamic criteria like paths, headers, and methods. Its integration with service registries like Eureka enables automatic load balancing, distributing traffic across multiple service instances to ensure reliability and availability.

The gateway enhances security by enforcing authentication, authorization, and rate-limiting policies consistently across microservices. It also manages cross-cutting concerns like logging, metrics, and tracing centrally, reducing the complexity for individual services.

Spring Cloud Gateway supports the API Gateway pattern, allowing it to aggregate responses from multiple services and simplify client interactions. Additionally, it offers performance optimizations by handling tasks like SSL termination and request transformations, which improve the efficiency of the microservices ecosystem.

Spring Gateway Requirements

Read first the following article, which contains a description of the initial microservices architecture: Getting started with Spring Cloud: Service Discovery

Next, we will add to the initial project a new Spring Boot project “gateway-proxy” with the following key dependencies:

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-gateway</artifactId>
	</dependency>
       <!-- Other dependencies -->
</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>

Here is a Sequence Diagram which shows the interaction between the various microservices in this project:

spring cloud gateway step-by-step guide

Next, let’s code our Gateway project.

Coding the Gateway Project

The key part of a Gateway project is the mapping of incoming calls to the microservices. You can either code it statically in your configuration (application.yml) or define a RouteLocator in your @Configuration Bean.

For example:

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;
import org.springframework.http.HttpMethod;


@Configuration
class ProxyConfig {

    @Bean
    RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("users_service_route",
                        route -> route.path("/user-service/**")
                                .and()
                                .method(HttpMethod.POST)
                                .filters(filter -> filter.stripPrefix(1)
                                )
                                .uri("lb://user-service")).build();
    }
}
  • The Route Locator defines a route with the ID "users_service_route". This ID is simply a name to identify the route.
  • route.path("/user-service/**"): Specifies that this route maps the incoming request path starting with /user-service/
  • and().method(HttpMethod.POST): Adds a condition that the route will only match if the HTTP method is POST.
  • filters(filter -> filter.stripPrefix(1)): Applies a filter to the route. The stripPrefix(1) filter removes the first segment of the path before forwarding the request to the downstream service.
  • uri("lb://user-service"): Specifies the destination URI for the route. The lb:// prefix indicates that this URI should be resolved using Spring Cloud’s load balancer, meaning it will forward the request to an instance of the user-service microservice.

Modify microservices to use the Gateway

Within your B2B microservices call, you need to modify your caller services to use the proxy instead of calling directly the target microservices. In our case, the card-service will go through the proxy gateway to reach the user-service.

To do that, modify the UserServiceClient Class in your card-service project:

@Component
public class UserServiceClient {

	private final RestTemplate restTemplate;
	private final DiscoveryClient discoveryClient;


	UserServiceClient(@Qualifier("restTemplate") RestTemplate restTemplate,
			DiscoveryClient discoveryClient) {
		this.restTemplate = restTemplate;
		this.discoveryClient = discoveryClient;
	}
 
	public ResponseEntity<User> registerUser(CardApplicationDto.User userDto) {

		ServiceInstance instance = discoveryClient.getInstances("proxy")
				.stream().findAny()
				.orElseThrow(() -> new IllegalStateException("Proxy unavailable"));

		return restTemplate.postForEntity(instance.getUri().toString()
						+ "/user-service/registration",
				userDto,
				User.class);
	}
}
  • As you can see, we will discover the “proxy” Service through the Eureka Server first.
  • Then, we will call the “/proxy/user-service/registration” URI.

If you go back to the RouteLocator you will see that it uses the stripPrefix to remove the “/proxy” section and forward the call to the “/user-service/registration

Test the Application Stack with the Gateway

Next, we will restart our Microservices stack. When all services are up and running, you should be able to see in your Eureka Console the user-service, the card-service and the gateway-proxy:

spring gateway step-by-step guide

You can call the front-end microservice and verify that the call hits the proxy before going to the user-service:

curl -X POST -H "Content-Type: application/json" http://localhost:9080/application -d @cardApplication.json

You should be able to see in your proxy Console a similar output:

2024-08-27T16:21:24.080+02:00 DEBUG 2596 --- [proxy] [ctor-http-nio-2] [
 ] .f.h.o.ObservedResponseHttpHeadersFilter : The response was handled for observation {name=http.client.requests(null), error=null, context=name='http.client.requests', contextualName='null', error='null', lowCardinalityKeyValues=[http.method='POST', http.status_code='UNKNOWN', spring.cloud.gateway.route.id='users_service_route', spring.cloud.gateway.route.uri='lb://user-service'], highCardinalityKeyValues=[http.uri='http://10.10.11.32:9083/registration']}}

Load Balancing Calls with the Gateway

If you pay attention to the RouteLocator definition, you will see it specifies the destination URI with the lb:// prefix . This indicates that this URI will forward and load balance the request to an instance of the user-service microservice.

You can easily test load balancing by creating a clone of the user-service project. For example, copy and paste the folder into user-service2 .

Within your user-service2 project, choose a different port to avoid conflicts with the existing user-service:

spring:
  application:
    name: user-service

server:
  address: 0.0.0.0
  port: 9088

Upon start of the user-service2 application, you should see two instances of the user-service in the Eureka Console:

spring gateway load balancing

Finally, by calling the card-service front-end, you should see from the Console logs that each call is load balanced across the user-service instances:

curl -X POST -H "Content-Type: application/json" http://localhost:9080/application -d @cardApplication.json

Source code: https://github.com/fmarchioni/masterspringboot/tree/master/cloud/cloud-gateway

Conclusion

Spring Cloud Gateway is a critical component in the Spring Cloud ecosystem. It provides centralized control over routing, security, and cross-cutting concerns, thereby simplifying the management of microservices and enhancing the overall robustness and scalability of the system. As microservices architectures continue to grow in complexity, a robust gateway solution like Spring Cloud Gateway becomes indispensable for maintaining a reliable, scalable, and secure system.

Found the article helpful? if so please follow us on Socials
Twitter Icon       Facebook Icon       LinkedIn Icon       Mastodon Icon