feat: Microservices pattern : Self-Registration (#3245)

* Services and information

* README.md updated

* unit tests added for controllers

* pom.xml, clean up, java version update and slf4j logging added

* pom files updated

* Removed children maven wrappers

* Microservice-self-registration added to pom file

* POM file updated for self-registration

* pom file updated

* Update pom.xml

correction to mudule name

* SonarQube comments addressed

* sonarqube comments on coverage addressed

* Code coverage for custom health checks

* changes for unit tests

* changes for unit tests

* changes to for code coverage improvement
This commit is contained in:
Naman Srivastava authored and GitHub committed 2026-02-08 20:40:07 +02:00
1 parent 3ba2560e09
commit 812e4cd6a2
39 files changed
+1049

No files matched your search

@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf
@@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> </parent>
<groupId>com.learning</groupId>
<artifactId>contextservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>contextservice</name>
<description>contextservice</description>
<properties>
<spring-cloud.version>2024.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
<scope>provided</scope>
</dependency>
<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-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,17 @@
package com.learning.contextservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ContextserviceApplication {
public static void main(String[] args) {
SpringApplication.run(ContextserviceApplication.class, args);
}
}
@@ -0,0 +1,42 @@
package com.learning.contextservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component("myCustomHealthCheck")
public class MyCustomHealthCheck implements HealthIndicator {
private static final Logger log = LoggerFactory.getLogger(MyCustomHealthCheck.class);
private volatile boolean isHealthy = true;
@Scheduled(fixedRate = 5000) // Run every 5 seconds
public void updateHealthStatus() {
// Perform checks here to determine the current health
// For example, check database connectivity, external service availability, etc.
isHealthy = performHealthCheck();
log.info("Update health status : {}", isHealthy);
}
boolean performHealthCheck() {
boolean current = System.currentTimeMillis() % 10000 < 5000; // Simulate fluctuating health
log.debug("Performing health check, current status: {}", current);
return current; // Simulate fluctuating health
}
@Override
public Health health() {
if (isHealthy) {
log.info("Health check successful, service is UP");
return Health.up().withDetail("message", "Service is running and scheduled checks are OK").build();
} else {
log.warn("Health check failed, service is DOWN");
return Health.down().withDetail("error", "Scheduled health checks failed").build();
}
}
}
@@ -0,0 +1,11 @@
package com.learning.contextservice.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "greetingservice")
public interface GreetingServiceClient {
@GetMapping("/greeting")
String getGreeting();
}
@@ -0,0 +1,26 @@
package com.learning.contextservice.controller;
import com.learning.contextservice.client.GreetingServiceClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ContextController {
private final GreetingServiceClient greetingServiceClient;
private final String userRegion;
@Autowired
public ContextController(GreetingServiceClient greetingServiceClient, @Value("${user.region}") String userRegion) {
this.greetingServiceClient = greetingServiceClient;
this.userRegion = userRegion;
}
@GetMapping("/context")
public String getContext() {
String greeting = greetingServiceClient.getGreeting();
return "The Greeting Service says: "+greeting+" from "+userRegion;
}
}
@@ -0,0 +1,25 @@
server:
port: 8082
spring:
application:
name: contextservice
eureka:
client:
service-url.defaultZone: http://localhost:8761/eureka
user:
region: Chennai, Tamil Nadu, India
management:
endpoint:
health:
show-details: always
web:
exposure:
include: health
logging:
file:
name: application.log
@@ -0,0 +1,49 @@
package com.learning.contextservice;
import com.learning.contextservice.client.GreetingServiceClient;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@SpringBootTest(classes = ContextserviceApplication.class)
@AutoConfigureMockMvc
@Import(TestConfig.class)
class ContextControllerTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private GreetingServiceClient greetingServiceClient;
@Value("${user.region}")
private String userRegion;
@Test
void shouldReturnContextGreeting() throws Exception{
Mockito.when(greetingServiceClient.getGreeting()).thenReturn("Mocked Hello");
mockMvc.perform(MockMvcRequestBuilders.get("/context")
.accept(MediaType.TEXT_PLAIN))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("The Greeting Service says: Mocked Hello from Chennai, Tamil Nadu, India"));
}
@Test
void shouldReturnContextServiceHealthStatusUp() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/actuator/health"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("\"status\":\"UP\"")));
}
}
@@ -0,0 +1,17 @@
package com.learning.contextservice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ContextserviceApplicationTests {
@Test
void contextLoads() {
// This is a basic integration test that checks if the Spring Application Context loads successfully.
// If the context loads without any exceptions, the test is considered passing.
// It is often left empty as the act of loading the context is the primary verification.
// You can add specific assertions here if you want to verify the presence or state of certain beans.
}
}
@@ -0,0 +1,17 @@
package com.learning.contextservice;
import com.learning.contextservice.client.GreetingServiceClient;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestConfig {
@Bean
public GreetingServiceClient greetingServiceClient() {
GreetingServiceClient mockClient = Mockito.mock(GreetingServiceClient.class);
Mockito.when(mockClient.getGreeting()).thenReturn("Mocked Hello");
return mockClient;
}
}
@@ -0,0 +1,33 @@
package com.learning.contextservice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Health;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.boot.actuate.health.Status;
import static org.junit.jupiter.api.Assertions.*;
class MyCustomHealthCheckTest {
@Test
void testHealthUp() {
MyCustomHealthCheck healthCheck = new MyCustomHealthCheck();
// Simulate a healthy state
ReflectionTestUtils.setField(healthCheck, "isHealthy", true);
Health health = healthCheck.health();
assertEquals(Status.UP, health.getStatus());
assertTrue(health.getDetails().containsKey("message"));
assertEquals("Service is running and scheduled checks are OK", health.getDetails().get("message"));
}
@Test
void testHealthDown() {
MyCustomHealthCheck healthCheck = new MyCustomHealthCheck();
// Simulate an unhealthy state
ReflectionTestUtils.setField(healthCheck, "isHealthy", false);
Health health = healthCheck.health();
assertEquals(Status.DOWN, health.getStatus());
assertTrue(health.getDetails().containsKey("error"));
assertEquals("Scheduled health checks failed", health.getDetails().get("error"));
}
}