DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

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

Trending

  • A System Cannot Protect What It Does Not Understand
  • Build a GitHub Slack Bot With AWS Bedrock and MCP, Part 2
  • Testing AI-Infused Apps: A Dual-Layer Framework for AI Quality Assurance
  • Beyond Manual Annotation: Engineering Self-Correcting Pseudo-Labeling Pipelines
  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.

By 
Vishnu Viswambharan user avatar
Vishnu Viswambharan
·
Sep. 15, 25 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
4.6K 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.

Consul dashboard

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

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:

First API call

Upon the second API call:

Second API callWe 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.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook