Spring Microservices
1. Service Discovery (Eureka)
Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
// application.yml
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 0
Eureka Client
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
// application.yml
spring:
application:
name: user-service
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
preferIpAddress: true
2. API Gateway (Spring Cloud Gateway)
Gateway Configuration
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_service", r -> r
.path("/api/users/**")
.filters(f -> f
.rewritePath("/api/users/(?<segment>.*)", "/${segment}")
.addRequestHeader("X-Gateway-Request", "true")
.circuitBreaker(config -> config
.setName("userServiceCircuitBreaker")
.setFallbackUri("forward:/fallback/users")))
.uri("lb://user-service"))
.route("order_service", r -> r
.path("/api/orders/**")
.filters(f -> f
.rewritePath("/api/orders/(?<segment>.*)", "/${segment}")
.retry(config -> config
.setRetries(3)
.setMethods(HttpMethod.GET)))
.uri("lb://order-service"))
.build();
}
@Bean
public CircuitBreakerFactory circuitBreakerFactory() {
Customizer<Resilience4JCircuitBreakerFactory> customizer =
factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.slidingWindowSize(10)
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(10))
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(2))
.build())
.build());
return new Resilience4JCircuitBreakerFactory(customizer);
}
}
Fallback Controller
@RestController
@RequestMapping("/fallback")
public class FallbackController {
@GetMapping("/users")
public ResponseEntity<Map<String, String>> userServiceFallback() {
Map<String, String> response = new HashMap<>();
response.put("message", "User Service is currently unavailable");
response.put("timestamp", Instant.now().toString());
return ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(response);
}
}
3. Config Server
Config Server Setup
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
// application.yml
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-org/config-repo
searchPaths: '{application}'
default-label: main
encrypt:
enabled: true
Config Client
@SpringBootApplication
@EnableConfigurationProperties
public class ServiceApplication {
@Value("${custom.property}")
private String customProperty;
@RefreshScope
@RestController
class ConfigController {
@GetMapping("/config")
public Map<String, String> getConfig() {
return Map.of("customProperty", customProperty);
}
}
}
// bootstrap.yml
spring:
application:
name: user-service
cloud:
config:
uri: http://localhost:8888
fail-fast: true
retry:
max-attempts: 6
initial-interval: 1000
max-interval: 2000
multiplier: 1.1
4. Circuit Breaker (Resilience4j)
Circuit Breaker Configuration
@Configuration
public class Resilience4jConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofMillis(1000))
.slidingWindowSize(2)
.build();
return CircuitBreakerRegistry.of(circuitBreakerConfig);
}
@Bean
public TimeLimiterRegistry timeLimiterRegistry() {
TimeLimiterConfig config = TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(2))
.build();
return TimeLimiterRegistry.of(config);
}
@Bean
public Retry retry() {
RetryConfig config = RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofMillis(100))
.retryExceptions(Exception.class)
.build();
return RetryRegistry.of(config).retry("default");
}
}
Service Implementation
@Service
public class UserService {
private final CircuitBreaker circuitBreaker;
private final TimeLimiter timeLimiter;
private final Retry retry;
public UserService(CircuitBreakerRegistry circuitBreakerRegistry,
TimeLimiterRegistry timeLimiterRegistry,
Retry retry) {
this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("userService");
this.timeLimiter = timeLimiterRegistry.timeLimiter("userService");
this.retry = retry;
}
public User getUser(Long id) {
return CircuitBreaker.decorateSupplier(circuitBreaker,
() -> retry.executeSupplier(
() -> userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id))
)
).get();
}
public CompletableFuture<User> getUserAsync(Long id) {
return TimeLimiter.decorateFutureSupplier(timeLimiter,
() -> CompletableFuture.supplyAsync(
() -> getUser(id)
)
);
}
}
5. Distributed Tracing (Sleuth & Zipkin)
Tracing Configuration
@Configuration
public class TracingConfig {
@Bean
public Sampler defaultSampler() {
return Sampler.ALWAYS_SAMPLE;
}
@Bean
public SpanCustomizer spanCustomizer(Tracer tracer) {
return tracer.currentSpan();
}
}
Service with Tracing
@Service
public class OrderService {
private final Tracer tracer;
private final UserClient userClient;
public OrderService(Tracer tracer, UserClient userClient) {
this.tracer = tracer;
this.userClient = userClient;
}
public Order createOrder(OrderRequest request) {
Span span = tracer.nextSpan().name("createOrder");
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span.start())) {
span.tag("userId", request.getUserId().toString());
// Validate user
User user = userClient.getUser(request.getUserId());
span.tag("userStatus", user.getStatus().toString());
// Create order
Order order = new Order();
order.setUserId(user.getId());
order.setItems(request.getItems());
order.setStatus(OrderStatus.CREATED);
return orderRepository.save(order);
} finally {
span.finish();
}
}
}
6. Best Practices
1. Service Communication
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
@PostMapping("/users")
User createUser(@RequestBody User user);
}
@Component
public class UserClientFallback implements UserClient {
@Override
public User getUser(Long id) {
return new User(); // Default user or throw exception
}
@Override
public User createUser(User user) {
throw new ServiceUnavailableException("User service is down");
}
}
2. Service Registry
@Configuration
public class ServiceRegistryConfig {
@Bean
public EurekaInstanceConfigBean eurekaInstanceConfig(
InetUtils inetUtils,
@Value("${spring.application.name}") String appName) {
EurekaInstanceConfigBean config = new EurekaInstanceConfigBean(inetUtils);
AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
config.setDataCenterInfo(info);
config.setHostname(info.get(AmazonInfo.MetaDataKey.publicHostname));
config.setIpAddress(info.get(AmazonInfo.MetaDataKey.publicIpv4));
config.setNonSecurePort(8080);
config.setAppname(appName);
config.setInstanceId(info.get(AmazonInfo.MetaDataKey.instanceId));
return config;
}
}
3. API Versioning
@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {
@GetMapping("/{id}")
public UserV1 getUserV1(@PathVariable Long id) {
return userService.getUserV1(id);
}
}
@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
@GetMapping("/{id}")
public UserV2 getUserV2(@PathVariable Long id) {
return userService.getUserV2(id);
}
}
4. Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").hasRole("ADMIN")
.antMatchers("/api/v1/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthoritiesClaimName("roles");
converter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtConverter;
}
}
5. Monitoring
@Configuration
public class MonitoringConfig {
@Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
@Bean
public CountedAspect countedAspect(MeterRegistry registry) {
return new CountedAspect(registry);
}
}
@Service
public class MetricsService {
private final MeterRegistry registry;
public MetricsService(MeterRegistry registry) {
this.registry = registry;
}
@Timed(value = "service.operation.time", description = "Time taken to execute service operation")
@Counted(value = "service.operation.count", description = "Number of service operations")
public void performOperation() {
// Business logic
}
public void recordMetric(String name, double value, String... tags) {
registry.gauge(name, value);
}
public void incrementCounter(String name, String... tags) {
registry.counter(name, tags).increment();
}
}