package com.example.demo.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@FeignClient(name = "user-service", fallback = UserServiceFallback.class)
public interface UserServiceClient {
@GetMapping("/api/users/{id}")
User getUserById(@PathVariable("id") Long id);
@GetMapping("/api/users")
List<User> getAllUsers();
}
@Component
class UserServiceFallback implements UserServiceClient {
@Override
public User getUserById(Long id) {
// Return default or cached data
User fallbackUser = new User();
fallbackUser.setId(id);
fallbackUser.setName("Service Unavailable");
return fallbackUser;
}
@Override
public List<User> getAllUsers() {
return Collections.emptyList();
}
}
package com.example.demo.service;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.CompletableFuture;
@Service
public class ResilientService {
private final RestTemplate restTemplate;
public ResilientService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@CircuitBreaker(name = "userService", fallbackMethod = "fallbackGetUser")
@Retry(name = "userService")
public User getUser(Long id) {
String url = "http://user-service/api/users/" + id;
return restTemplate.getForObject(url, User.class);
}
@TimeLimiter(name = "userService")
@CircuitBreaker(name = "userService", fallbackMethod = "fallbackGetUserAsync")
public CompletableFuture<User> getUserAsync(Long id) {
return CompletableFuture.supplyAsync(() -> {
String url = "http://user-service/api/users/" + id;
return restTemplate.getForObject(url, User.class);
});
}
private User fallbackGetUser(Long id, Exception ex) {
System.err.println("Fallback for user " + id + ": " + ex.getMessage());
User user = new User();
user.setId(id);
user.setName("Fallback User");
return user;
}
private CompletableFuture<User> fallbackGetUserAsync(Long id, Exception ex) {
return CompletableFuture.completedFuture(fallbackGetUser(id, ex));
}
}
spring:
application:
name: user-service
cloud:
config:
uri: http://config-server:8888
fail-fast: true
eureka:
client:
serviceUrl:
defaultZone: http://eureka-server:8761/eureka/
fetch-registry: true
register-with-eureka: true
instance:
prefer-ip-address: true
resilience4j:
circuitbreaker:
instances:
userService:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
retry:
instances:
userService:
maxAttempts: 3
waitDuration: 1s
timelimiter:
instances:
userService:
timeoutDuration: 2s
Spring Cloud provides tools for building distributed microservices. I use Spring Cloud Netflix Eureka for service discovery—services register themselves and discover others dynamically. Ribbon enables client-side load balancing. Feign creates declarative REST clients with interface definitions. Spring Cloud Config externalizes configuration to a central server. Circuit breakers with Resilience4j prevent cascade failures. API Gateway routes requests and handles cross-cutting concerns like authentication. Distributed tracing with Sleuth and Zipkin correlates logs across services. Spring Cloud Stream connects services via message brokers. Configuration refresh updates properties without restart. These patterns address microservices challenges—service location, resilience, distributed configuration, and observability—enabling scalable, fault-tolerant systems.