Spring Cloud Consul

  • Spring Cloud Consul integrates Consul with Spring Boot applications, facilitating service discovery, configuration management, and distributed system coordination.
  • Leverages the open-source Consul platform for managing complex microservice architectures, enhancing service resilience and ease of scaling.
graph TD A[Spring Cloud Consul] --> B[Service Discovery] A --> C[Configuration Management] A --> D[Health Checking] A --> E[Load Balancing] B --> F[Service Registration] B --> G[Service Discovery API] C --> H[Centralized Config Storage] C --> I[Dynamic Config Updates] D --> J[Health Check Monitors] D --> K[Service Status Tracking] E --> L[Traffic Distribution] E --> M[Failover Handling] style A fill:#2b6cb0,stroke:#ffffff,stroke-width:2px style B fill:#4caf50,stroke:#ffffff,stroke-width:1px style C fill:#ff9800,stroke:#ffffff,stroke-width:1px style D fill:#2196f3,stroke:#ffffff,stroke-width:1px style E fill:#9c27b0,stroke:#ffffff,stroke-width:1px

Install and Start Consul

  • Download Consul: https://developer.hashicorp.com/consul/install
  • Unzip the downloaded file.
  • add the location to your PATH for Windows).
  • consul agent -dev
  • it will run on localhost:8500.
  • Access the Consul UI: http://localhost:8500 to access the Consul UI.

Tp1: Service Discovery

1. Client-A Application

Step 2.1: Create a Spring Boot Project for Client-A
Dependencies:
  • Spring Web
  • Spring Cloud Starter Consul Discovery
  • Spring Boot DevTools (optional for easier development)
pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
</dependencies>

Step 2.2: Configure application.yml for Client-A

Create a file named application.yml in the src/main/resources:

spring:
  application:
    name: client-a
  cloud:
    consul:
      host: localhost
      port: 8500
	  

Step 2.3: Create a REST Controller for Client-A

Create a REST controller that exposes an endpoint:

package com.example.clienta;

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

@RestController
public class ClientAController {
    
    @GetMapping("/greet")
    public String greet() {
        return "Hello from Client A!";
    }
}

Step 2.4: Create the Main Application Class


package com.example.clienta;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Run Client A

2. Client-B Application Setup

Create a Spring Boot Project for Client-B

Similar to Client-A, create another Spring Boot project for Client-B with the same dependencies.

pom.xml


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
</dependencies>

application.yml for Client-B

Create a file named application.yml in the src/main/resources directory with the following content:

spring:
  application:
    name: client-b
  cloud:
    consul:
      host: localhost
      port: 8500
	  

Create a REST Controller for Client-B

Create a REST controller that calls Client-A:

package com.example.clientb;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class ClientBController {

    @Autowired
    private DiscoveryClient discoveryClient;

    private final RestTemplate restTemplate = new RestTemplate();

    @GetMapping("/call-a")
    public String callClientA() {
        List<ServiceInstance> instances = discoveryClient.getInstances("client-a");
        if (instances != null && !instances.isEmpty()) {
            String clientAUrl = instances.get(0).getUri().toString() + "/greet";
            return restTemplate.getForObject(clientAUrl, String.class);
        }
        return "Client A not available";
    }
}
 
  • DiscoveryClient: Used for service discovery. It provides information about registered services (such as their URLs) and allows applications to locate other services by name.
  • RestTemplate: Used for making HTTP requests. After obtaining the service URL from DiscoveryClient, RestTemplate uses this URL to make RESTful calls to the discovered service.
  • Client-B uses DiscoveryClient to retrieve the URL of Client-A from the Consul Server.
  • DiscoveryClientcontacts Consul Server to fetch the URL of Client-A.
  • RestTemplate then uses the retrieved URL to make HTTP requests to Client-A.
graph TD ClientB["Client-B Application"] -->|Uses| DiscoveryClient["DiscoveryClient"] DiscoveryClient -->|Finds service instance URL for 'client-a'| ClientAURL["client-a URL"] ClientB -->|Uses| RestTemplate["RestTemplate"] RestTemplate -->|Makes HTTP request to| ClientAURL ClientB --> ConsulServer["Consul Server"] DiscoveryClient -->|Fetches service information from| ConsulServer ClientAURL -->|Registered with| ConsulServer

Create the Main Application Class for Client-B


package com.example.clientb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Run

  • Run Consul
  • Run Client-A:
  • Run Client-B:
Go to http://localhost:8500 to see both Client-A and Client-B registered.
Test the API: Use a tool like Postman or cURL to test the endpoints.

Call Client-A: http://localhost:8080/greet
Call Client-B :that call A :http://localhost:8081/call-a
You should see:

The response from Client-A when hitting the /greet endpoint.
The response from Client-B calling Client-A through service discovery.

Configuration Management

  1. Consul: Acts as a centralized configuration storage where configurations (key-value pairs) for applications are stored.

  2. Spring Boot Application: The application loads configurations from Consul when it starts and injects them into the @ConfigurationProperties class (e.g., AppConfig).

  3. @RefreshScope: This annotation allows specific beans, such as AppConfig, to be refreshed with updated values when a configuration refresh is triggered.

  4. Actuator /refresh Endpoint: When a POST request is sent to /actuator/refresh, Spring Cloud reloads the configurations from Consul without restarting the application.

  5. User/Admin: Can update configuration values directly in Consul and then trigger the /refresh endpoint, making the updated configurations immediately available in the application.

graph TD Consul["Consul Server (Central Config Storage)"] -->|Stores config values| KeyValueStore["Key-Value Store"] ClientApp["Spring Boot Application"] -->|Loads config on startup| ConfigProperties["@ConfigurationProperties (AppConfig)"] ClientApp --> ActuatorRefresh["/actuator/refresh Endpoint"] KeyValueStore -->|Provides config values| ClientApp ConfigProperties -->|Holds config values| RefreshScope["@RefreshScope"] User["User/Admin"] -->|Updates config| KeyValueStore User -->|Triggers refresh| ActuatorRefresh ActuatorRefresh -->|Refreshes updated config| ConfigProperties

Create Configuration

Create Configuration in Consul Key-Value Store
  • Open the Consul UI at http://localhost:8500.
  • Navigate to Key/Value.
  • Create a key-value pair :config/application-name/data
  • config/client-a/messge/
  • use cmd :consul kv put config/client-a/message "client a message"

Create the Spring Boot Application

including the following dependencies:
  • Spring Web (to create REST endpoints)
  • Spring Cloud Starter Consul Config (for Consul integration)
  • Spring Boot Actuator (for monitoring and refreshing)

pom.xml

:

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-discovery</artifactId>
		</dependency>
		<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
		  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

Configure application.yml for Consul

spring:
  application:
    name: clienta

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        enabled: true
      config:
        enabled: true
        prefix: config

  config:
    import: "consul:"

management:
  endpoints:
    web:
      exposure:
        include: health,info,refresh

server:
  port: 8080


Use Consul Configuration in the Application


package com.example.clienta;

import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@RefreshScope
@Component

public class AppConfig {
	@Value("${message}")
	private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
To make the configuration update dynamically, we’ll use Spring Cloud’s @RefreshScope annotation and Spring Boot Actuator’s /refresh endpoint.

Create a REST Controller to Display the Configuration


package com.example.clienta;

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

@RestController
public class ConfigController {

    private final AppConfig appConfig;

    public ConfigController(AppConfig appConfig) {
        this.appConfig = appConfig;
    }

    @GetMapping("/message")
    public String getMessage() {
        return appConfig.getMessage();
    }
}

Run

Enable Actuator’s Refresh Endpoint In application.yml, enable the /actuator/refresh endpoint by adding the following configuration:

management:
  endpoints:
    web:
      exposure:
        include: refresh

Run

http://localhost:8080/message
You should see the value of message as set in Consul (Hello from Consul Config!).
Update the Configuration in Consul: Go to http://localhost:8500. Update the message key under config/client-a/data to a new value, like "Hello, updated from Consul!".
Refresh the Configuration: you can
  • 1.Restart the application
  • 2.send a POST request to the /actuator/refresh endpoint:
     POST http://localhost:8080/actuator/refresh
    http://localhost:8080/message