Cómo configurar un API Gateway en Spring Cloud

En una arquitectura moderna basada en microservicios, tener múltiples servicios expuestos directamente al cliente genera caos y vulnerabilidades. Aquí es donde entra en juego el API Gateway, actuando como una puerta única de entrada que enruta las peticiones al microservicio correspondiente.

 

 

Spring Cloud nos ofrece una solución sencilla, aunque potente: Spring Cloud Gateway, un gateway reactivo diseñado para gestionar rutas, filtros y descubrimiento de servicios con una configuración mínima.

 

En el post de hoy vamos a tratar de explicar cómo crear un Gateway sencillo que enrute hacia tres microservicios distintos, paso a paso.

 

¿Qué es Spring Cloud Gateway?

 

Spring Cloud Gateway es el gateway oficial de Spring para aplicaciones construidas con Spring Bot 3 o superior (reemplazó al gateway Zuul que se utilizaba en versiones más antiguas de Spring Boot). Ofrece un modelo de programación reactivo, gracias a Spring WebFlux, y permite:

  • Gestionar rutas dinámicas.

  • Aplicar filtros (por ejemplo, autenticación o logging).

  • Integrarse con Eureka para descubrir servicios automáticamente.

  • Configurar rutas mediante parametrización (ficheros YAML o properties) o de forma programática (código Java).

 

 

A continuación, vamos a ir detallando los pasos necesarios para crear un servicio Spring Gateway que quede operativo con una configuración básica. 

 

➕ Paso 1: Crear el proyecto y añadir dependencias

 

En primer lugar, hay que crear un proyecto Spring Boot. Usa Spring Initializr o tu editor favorito y asegúrate de incluir las siguientes dependencias Maven:

  • Spring Reactive Web
  • Spring Reactive Gateway
  • Eureka Discovery Client 

  

 

Las dependencias deberían quedar configuradas del siguiente modo: 

 
 
<dependencies>
  <!-- Gateway reactivo -->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
  </dependency>

  <!-- Eureka Client (si usas Service Discovery) -->
  <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-webflux</artifactId>
</dependency>

  <!-- Actuator para monitorización (opcional) -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
</dependencies>
 
 
 

Por cierto, otra cosa, no olvides añadir la gestión de versiones de Spring Cloud en tu fichero de dependencias Maven "pom.xml":


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

➕ Paso 2: Configuración en application.properties

 
A continuación, procedemos a realizar la configuración básica de nuestro fichero application.properties, donde tendremos que especificar el puerto de escucha de nuestro servicio Gateway.
 
 
server.port=8765
spring.application.name=api-gateway

# Eureka si se utiliza Discovery
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

# Nivel de logging para observar el comportamiento del Gateway
logging.level.org.springframework.cloud.gateway=DEBUG
 
 
 

➕ Paso 3: Definir rutas para los microservicios

 

Para este ejemplo, vamos a imaginar que nuestra aplicación está compuesta por tres microservicios (users, orders y products) que están registrados en un servidor Eureka:

  • users-service → puerto 8081

  • orders-service → puerto 8082

  • products-service → puerto 8083

 

Si añadimos la gestión de las rutas de estos microservicios, la configuración de nuestro fichero application.properties quedaría del siguiente modo:

 

 
# Configuración general
server.port=8765
spring.application.name=api-gateway

# Configuración de Eureka
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

# Rutas Gateway
spring.cloud.gateway.routes[0].id=users-service
spring.cloud.gateway.routes[0].uri=lb://users-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/users/**
spring.cloud.gateway.routes[0].predicates[1]=Method=GET

spring.cloud.gateway.routes[1].id=orders-service
spring.cloud.gateway.routes[1].uri=lb://orders-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/orders/**
spring.cloud.gateway.routes[1].predicates[1]=Method=GET

spring.cloud.gateway.routes[2].id=products-service
spring.cloud.gateway.routes[2].uri=lb://products-service
spring.cloud.gateway.routes[2].predicates[0]=Path=/products/**
spring.cloud.gateway.routes[2].predicates[1]=Method=GET

 

👉 Como vemos en el fichero anterior, ahí estamos gestionando tres rutas

  • Si se recibe un petición GET para la ruta "/users/**", la ejecución se redirige al balanceador del microservicio "users-service"    
  • Si se recibe un petición GET para la ruta "/orders/**", la ejecución se redirige al balanceador del microservicio "orders-service"
  • Si se recibe un petición GET para la ruta "/products/**", la ejecución se redirige al balanceador del microservicio "products-service" 

 

❌ Hay que puntualizar una cosa: para redigir al Balanceador de un determinado microservicio, en la URI de la ruta debemos incluir el prefijo "lb://", tal y como acabamos de ver en la configuración del application.properties anterior.

  • lb://users-service
  • lb://orders-service
  • lb://products-service 

 

El fichero anterior también podría ser configurado en formato YAML, con idéntico resultado. En ese caso, el fichero application.yml tendría este aspecto:

 

 
server:
  port: 8080

spring:
  application:
    name: api-gateway

  cloud:
    gateway:
      routes:
        - id: users-service
          uri: lb://users-service
          predicates:
            - Path=/users/**
            - Method=GET

        - id: orders-service
          uri: lb://orders-service
          predicates:
            - Path=/orders/**
            - Method=GET

        - id: products-service
          uri: lb://products-service
          predicates:
            - Path=/products/**
            - Method=GET

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


Con esto, las peticiones que comiencen por /users/, /orders/ o /products/ serán redirigidas automáticamente al microservicio correspondiente.

 

➕ Paso 4 (opcional): Configuración automática de rutas

 

Si no queremos configurar las rutas de forma manual, Spring Gateway también dispone de la opción de enrutamiento autómatico. En ese caso, en vez de ir parametrizando las rutas una a una, simplemente tendremos que añadir los parámetros:

  • spring.cloud.gateway.discovery.locator.enabled
  • spring.cloud.gateway.discovery.locator.lower-case-service-id

 

De esta forma, eliminando las rutas manuales, el application.properties quedaría bastante simplificado. El fichero tendría este aspecto:

 
# Configuración general
server.port=8765
spring.application.name=api-gateway

# Configuración de Eureka (opcional, si se usa)
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

# Activacion de API gateway con enrutamiento automatico
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true

 

⛔ ¿Cómo funcionaría este enrutamiento automático? 

Tomemos, por ejemplo, el microservicio "users-service". Este microservicio estará registrado en el servidor Eureka con el nombre USERS-SERVICE.

Por tanto, en función del registro anterior, la invocación automática a ese microservicio se realizará en el Spring Gateway a través del endpoint /USERS-SERVICE.

Si, por ejemplo, quisiéramos llegar hasta la ruta "/currency-conversion" del microservicio "users-service", el endpoint de invocación sería el siguiente:

/users-service/currency-conversion

 

Del mismo modo, si quisiéramos llegar a la ruta "/currency-exchange" del microservicio "orders-service" (registrado en Eureka como ORDERS-SERVICE), entonces el endpoint de invocación sería el siguiente:

/orders-service/currency-exchange 

 

Como vemos, el enrutamiento automático simplifica mucho la gestión de rutas de los microservicios de nuestra aplicación. Eso sí, como contraprestación, no disponemos de la flexibilidad de configuración que ofrece el enrutamiento manual. 

 

➕ Paso 5: Clase principal del proyecto

 

Asegúrate de tener la clase principal del servicio Gateway correctamente configurada. Debería quedar algo como lo siguiente.

 
 
package com.example.apigateway;

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

@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}
 
  

Si usas descubrimiento de servicios con Eureka, puedes añadir @EnableDiscoveryClient, aunque en Spring Boot 3 ya no es obligatorio. 

 

➕ Paso 6 (opcional): Configurar rutas en Java

 

Si no te gusta parametrizar las rutas en el fichero de propiedades, Spring Cloud ofrece la posibilidad de configurar el enrutamiento de manera programática. Para ello, tendríamos que crearnos una clase GatewayConfig marcada con el tag @Configuration. 

@Configuration
public class GatewayConfig {

 

En dicha clase deberíamos crearnos un método (marcado con el tag @Bean) que deberá devolver un objeto de tipo RouteLocator donde estarán configurados diferentes enrutamientos.

@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder) {

 

Para cada enrutamiento que queramos realizar, nos creamos una función Lambda que redirija un determinado endpoint hacia un microservicio concreto.

// Funcion Lambda que redirige los paths /users al microservicio users-service
Function<PredicateSpec, Buildable<Route>> funcRouteUsers =
                        r -> r.path("/users/**").uri("lb://users-service");

 

Construimos el objeto RouteLocator con cada uno de los enrutamientos definidos. Una vez hecho lo anterior, el método marcado con el tag @Bean tendrá que devolver este objeto RouteLocator.

            RouteLocator routeLocator = builder.routes()
                    .route("users-service", funcRouteUsers)
                    .build();

 

🌑 Si incluimos la configuración necesaria para cada uno de los tres microservicios que estamos tratando en el ejemplo, la clase GatewayConfig debería quedar algo así como lo siguiente:

 

 
package com.appsdevblog.photoapp.api.gateway;

import java.util.function.Function;

import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.Buildable;
import org.springframework.cloud.gateway.route.builder.PredicateSpec;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GatewayConfig {

        @Bean
        public RouteLocator customRoutes(RouteLocatorBuilder builder) {
           
            // Funcion Lambda que redirige los paths /users al microservicio users
            Function<PredicateSpec, Buildable<Route>> funcRouteUsers =
                            r -> r.path("/users/**").uri("lb://users-service");
            // Funcion Lambda que redirige los paths /orders al microservicio orders
            Function<PredicateSpec, Buildable<Route>> funcRouteOrders =
                            r -> r.path("/orders/**").uri("lb://orders-service");
            // Funcion que redirige los paths /products al microservicio products
            Function<PredicateSpec, Buildable<Route>> funcRouteProducts =
                            r -> r.path("/products/**").uri("lb://products-service");
           
            RouteLocator routeLocator = builder.routes()
                    .route("users-service", funcRouteUsers)
                    .route("orders-service", funcRouteOrders)
                    .route("products-service", funcRouteProducts)
                    .build();
                                   
            return routeLocator;
           
        }
       
}

 

Esta opción es útil si necesitas lógica condicional o rutas generadas dinámicamente. En cualquier caso, ya sabes que, en relación con la configuración del Spring Gateway, dispones tanto de la opción parametrizada como de la opción programática.

 

 

➕ Paso 7 (opcional): Añadir Logs al Gateway

 

Para mostrar los logs del API Gateway, debemos usar Log4Java. Para ello, nos podemos crear un componente de tipo “GlobalFilter” como el siguiente y de este modo se irán registrando todas las invocaciones que se vayan realizando al API Gateway.

 

 
@Component
public class LoggingFilter implements GlobalFilter {

    private Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
   
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // TODO Auto-generated method stub
       
        // Registramos una traza en el log
        logger.info("Path of the request received -> {}", exchange.getRequest().getPath());
       
        // Dejamos que continue la ejecucion
        return chain.filter(exchange);
       
    }

}
 

  

Como vemos, esta clase está implementada con RFP (Programación Funcional Reactiva). Recordemos que el Gateway de Spring Cloud es reactivo y que, por tanto, todo el software incluido en dicho servicio debe realizarse bajo dicha directriz.

 

 

📌 Testing: Probar el Gateway

 

Asegúrate de tener los tres microservicios corriendo en sus respectivos puertos. Luego arranca el API Gateway y realiza pruebas para invocar a algunos endpoints de los microservicios configurados. Por ejemplo, podría ser algo así como:

 

curl http://localhost:8080/users/status curl http://localhost:8080/orders/list curl http://localhost:8080/products/details

 

Si todo está correctamente configurado, verás que cada una de las peticiones se redirigirá correctamente al microservicio definido.

 

 

Conclusión: Gateway sencillo, arquitectura limpia

 

Con sólo unas cuantas líneas de configuración, puedes montar un API Gateway funcional con Spring Cloud, capaz de enrutar tráfico hacia múltiples microservicios de forma eficiente y controlada. Esto no sólo reduce la complejidad en el cliente, sino que también mejora la seguridad, la observabilidad y la flexibilidad de tu sistema distribuido.

 

¡Nos vemos en el siguiente post!

Saludos.

 

Comentarios

Entradas populares de este blog

Componentes y Ventanas de Java Swing

Creación de Webservice SOAP mediante Anotaciones

Configurar Apache Tomcat en Eclipse