VOOZH about

URL: https://dzone.com/articles/spring-cloud-gateway-service-discovery-consul

⇱ Spring Cloud Gateway With Consul Service Discovery


Related

  1. DZone
  2. Coding
  3. Java
  4. Spring Cloud Gateway With Service Discovery Using HashiCorp Consul

Spring Cloud Gateway With Service Discovery Using HashiCorp Consul

This article introduces HashiCorp Consul, a service registry and discovery tool that integrates well with Spring Boot and supports reactive programming.

Likes
Comment
Save
4.7K Views

Join the DZone community and get the full member experience.

Join For Free

This article will explain some basics of the HashiCorp Consul service and its configurations. It is a service networking solution that provides service registry and discovery capabilities, which integrate seamlessly with Spring Boot. You may have heard of Netflix Eureka; here, Consul works similarly but offers many additional features. Notably, it supports the modern reactive programming paradigm. I will walk you through with the help of some applications.

Used Libraries

  • Spring Boot
  • Spring Cloud Gateway
  • Spring Cloud Consul
  • Spring Boot Actuator

The architecture includes three main components:

  1. Consul
  2. Service application
  3. Gateway

1. Consul

We have to download and install the Consul service in the system from the Hashicorp Consul official website. For development purposes, we have to start it using a command in PowerShell (in Windows).

PowerShell
consul agent -dev


Consul Dashboard

This is the place where we can see all the applications registered with Consul. The default port for accessing the Consul dashboard is 8500. Once it starts successfully, you will see something like below. 

The next step is to register the Gateway and Service applications to Consul. Once those are added, they will appear in this same dashboard. When multiple instances of the same service are running, Consul continuously monitors their health using "Actuator." If any of them report an unhealthy status, Consul will automatically deregister them from the registry.

2. Service Application

It is a simple service application for exposing the APIs. We added an @EnableDiscoveryClient annotation in the main class to register the service in Consul for service discovery. If you run the application under multiple ports then you can see multiple instances in consul dashboard. Used the  Actuator to expose the health status.

Main Class

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


Maven Configuration

XML
 <properties>
 <java.version>21</java.version>
 <spring.cloud.version>2023.0.4</spring.cloud.version>
 </properties>

 <dependencies>

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

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-webflux</artifactId>
 </dependency>

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

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
    </dependencies>


Application Property File

Properties files
 # Assigning a unique name for the service
 spring.application.name=service-app

 # Application will use random ports
 server.port=0

 spring.webflux.base-path=/userService
 logback.log.file.path=./logs/service


 # ~~~ Consul Configuration ~~~

 # It assigns a unique ID to each instance of the service when running multiple instances,
 # allowing them to be registered individually in Consul for service discovery.
 spring.cloud.consul.discovery.instance-id=${spring.application.name}-${server.port}-${random.int[1,99]}

 # To access centralized configuration data from Consul
 spring.cloud.consul.config.enabled=false

 # To register the service in Consul using its IP address instead of the hostname.
 spring.cloud.consul.discovery.prefer-ip-address=true

 # The service will register itself in Consul under this name, which the gateway will use for service discovery while routing requests.
 spring.cloud.consul.discovery.service-name=${spring.application.name}

 # Ip to communicate with consul server
 spring.cloud.consul.host=localhost

 # Consul runs on port 8500 by default, unless it is explicitly overridden in the configuration. 
 spring.cloud.consul.port=8500

 # Remapping the Actuator URL in Consul since a base path has been added.
 spring.cloud.consul.discovery.health-check-path=${spring.webflux.base-path}/actuator/health

 # Time interval to check the health of service.
 spring.cloud.consul.discovery.health-check-interval=5s

 # Time need to wait for the health check response before considering it as timed out
 spring.cloud.consul.discovery.health-check-timeout=5s

 # The maximum amount of time a service can remain in an unhealthy state before Consul marks it as critical and removes it from the service catalog.
    #spring.cloud.consul.discovery.health-check-critical-timeout=1m


Sample API

Java
 @GetMapping(value = "getStatus", produces = MediaType.APPLICATION_JSON_VALUE)
 public Mono<ResponseEntity<Object>> healthCheck() {
 logger.info("<--- Service to get status request : received --->");
 logger.info("<--- Service to get status response : given --->");
 return Mono.just(ResponseEntity.ok("Success from : " + portListener.getPort()));
    }


3. Gateway

It is developed with the help of Spring Cloud Gateway. And it consists of the same libraries as the Service application. Consul is used for registering and service discovery of the application. Used the Actuator to expose the health status.

Main Class

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


Maven Configuration

Java
<properties>
 <java.version>21</java.version>
 <spring.cloud.version>2023.0.4</spring.cloud.version>
 </properties>

 <dependencies>

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

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-webflux</artifactId>
 </dependency>

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

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>

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


Application Property File

Properties files
# Assigning a unique name for the service
 spring.application.name=gateway-app
 server.port=3000

 logback.log.file.path=./logs/gateway


 # ~~~ Consul Configuration ~~~

 # It is used in Spring Cloud Gateway to handle automatic route discovery from a service registry
 # When we are configuring as false, we have to explicitly configure routing of each API requests.
 spring.cloud.gateway.discovery.locator.enabled=false

 spring.cloud.consul.discovery.instance-id=${spring.application.name}-${server.port}-${random.int[1,99]}
 spring.cloud.consul.config.enabled=false
 spring.cloud.consul.discovery.prefer-ip-address=true
 spring.cloud.consul.discovery.service-name=${spring.application.name}
 spring.cloud.consul.host=localhost
    spring.cloud.consul.port=8500


Since we have set spring.cloud.gateway.discovery.locator.enabled to false, we need to explicitly configure the routing for each API request as shown below. For the routing destination URL, instead of specifying the actual URL of the service application, we map it to the load-balanced (lb) URL provided by Consul using the service name.

  • In normal gateway spring.cloud.gateway.routes[0].uri=http://192.168.1.10:5000
  • In service discovery enabled gateway spring.cloud.gateway.routes[0].uri=lb://service-app
Properties files
 #~~~ Example for a url routing ~~~

 
 spring.cloud.gateway.routes[0].id=0

 # Instead of configuring the actual url of service application, we are mapping in to the lb url of "consul" with service name. 
 spring.cloud.gateway.routes[0].uri=lb://service-app

 # Rest of the configuration will keep as same as spring cloud gateway configuration

 spring.cloud.gateway.routes[0].predicates[0]=Path=/userService/**
 spring.cloud.gateway.routes[0].filters[0]=RewritePath=/userService/(?<segment>.*), /userService/${segment}
    spring.cloud.gateway.routes[0].filters[1]=PreserveHostHeader


Final Consul Dashboard


Here, we can see one instance of gateway-app and two instances of service-app, as I am running two instances of the service app under different ports.

Testing

Let's test it by calling a sample API through the gateway to verify that it's working.

Upon the first API call:

Upon the second API call:

We can see that each time the API returns a response from a different instance.

GitHub

Please check here to get the full project. Thanks for reading!

API Service discovery Spring Cloud

Opinions expressed by DZone contributors are their own.

Related

  • How to Build a Real API Gateway With Spring Cloud Gateway and Eureka
  • Micronaut vs Spring Boot: A Detailed Comparison
  • Using Spring Cloud Gateway and Discovery Service for Seamless Request Routing
  • Microservices Discovery With Eureka

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

Let's be friends: