Inter-Service Communication
Mikroservis mimarisinde servisler birbirleriyle iletişim kurmak zorundadır. Sipariş servisi kullanıcı bilgisini almak için kullanıcı servisini çağırır, ödeme servisi sipariş detaylarını almak için sipariş servisini çağırır. Bu derste senkron iletişim yöntemleri olan RestClient, WebClient ve OpenFeign'i, load balancing entegrasyonunu ve hata yönetimini inceleyeceğiz.
Senkron vs Asenkron İletişim
Senkron (Request-Response): Servis A, Servis B'yi çağırır ve yanıtı bekler. HTTP/REST, gRPC.
Asenkron (Event-Driven): Servis A bir mesaj/event yayınlar, Servis B bunu kendi zamanında işler. RabbitMQ, Kafka.
Bu derste senkron iletişime odaklanacağız.
RestClient (Spring 6.1+)
Spring Framework 6.1 ile gelen modern, fluent HTTP client:
@Configuration
public class RestClientConfig {
@Bean
public RestClient userServiceClient(
RestClient.Builder builder,
@LoadBalanced ClientHttpRequestFactory requestFactory) {
return builder
.baseUrl("http://user-service")
.defaultHeader("Content-Type", "application/json")
.requestFactory(requestFactory)
.build();
}
}@Service
public class OrderService {
private final RestClient userClient;
public OrderService(RestClient userClient) {
this.userClient = userClient;
}
public UserDto getUser(Long userId) {
return userClient.get()
.uri("/api/users/{id}", userId)
.retrieve()
.body(UserDto.class);
}
public UserDto createUser(UserDto dto) {
return userClient.post()
.uri("/api/users")
.body(dto)
.retrieve()
.body(UserDto.class);
}
// Hata yönetimi
public UserDto getUserSafe(Long userId) {
return userClient.get()
.uri("/api/users/{id}", userId)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new UserNotFoundException("User not found: " + userId);
})
.onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
throw new ServiceUnavailableException("User service error");
})
.body(UserDto.class);
}
}WebClient (Reactive)
Reactive/non-blocking HTTP client. Spring WebFlux uygulamalarında tercih edilir:
@Configuration
public class WebClientConfig {
@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
@Bean
public WebClient userServiceWebClient(WebClient.Builder builder) {
return builder
.baseUrl("http://user-service")
.defaultHeader("Content-Type", "application/json")
.filter(ExchangeFilterFunctions.basicAuthentication(
"user", "password"))
.build();
}
}@Service
public class OrderService {
private final WebClient userClient;
public OrderService(WebClient userServiceWebClient) {
this.userClient = userServiceWebClient;
}
// Reactive (non-blocking)
public Mono<UserDto> getUserReactive(Long userId) {
return userClient.get()
.uri("/api/users/{id}", userId)
.retrieve()
.bodyToMono(UserDto.class)
.timeout(Duration.ofSeconds(3))
.retry(2)
.onErrorReturn(new UserDto(userId, "Fallback", "N/A"));
}
// Blocking (synchronous wrapper)
public UserDto getUser(Long userId) {
return getUserReactive(userId).block();
}
// Birden fazla servis çağrısını paralel yapma
public OrderDetails getOrderDetails(Long orderId) {
Order order = orderRepo.findById(orderId).orElseThrow();
Mono<UserDto> userMono = userClient.get()
.uri("/api/users/{id}", order.getUserId())
.retrieve().bodyToMono(UserDto.class);
Mono<ProductDto> productMono = productClient.get()
.uri("/api/products/{id}", order.getProductId())
.retrieve().bodyToMono(ProductDto.class);
// İki çağrı paralel çalışır
return Mono.zip(userMono, productMono)
.map(tuple -> new OrderDetails(
order, tuple.getT1(), tuple.getT2()))
.block();
}
}OpenFeign — Declarative HTTP Client
OpenFeign, sadece bir interface tanımlayarak diğer servisleri çağırmanızı sağlar. İmplementasyonu Spring otomatik oluşturur:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication { }Feign client interface:
@FeignClient(
name = "user-service",
fallbackFactory = UserClientFallbackFactory.class
)
public interface UserClient {
@GetMapping("/api/users/{id}")
UserDto getUser(@PathVariable Long id);
@GetMapping("/api/users")
List<UserDto> getAllUsers();
@PostMapping("/api/users")
UserDto createUser(@RequestBody UserDto dto);
@GetMapping("/api/users/search")
Page<UserDto> searchUsers(
@RequestParam("query") String query,
@RequestParam("page") int page,
@RequestParam("size") int size);
}Kullanımı son derece basittir:
@Service
public class OrderService {
private final UserClient userClient;
public OrderService(UserClient userClient) {
this.userClient = userClient;
}
public OrderDto createOrder(OrderRequest request) {
// Feign client kullanımı — normal metod çağrısı gibi!
UserDto user = userClient.getUser(request.getUserId());
Order order = new Order();
order.setUserId(user.getId());
order.setUserName(user.getName());
// ...
return orderMapper.toDto(orderRepo.save(order));
}
}Feign Fallback (Hata Yönetimi)
@Component
public class UserClientFallbackFactory
implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public UserDto getUser(Long id) {
return new UserDto(id, "Unknown", "N/A");
}
@Override
public List<UserDto> getAllUsers() {
return Collections.emptyList();
}
@Override
public UserDto createUser(UserDto dto) {
throw new ServiceUnavailableException(
"User service unavailable");
}
@Override
public Page<UserDto> searchUsers(
String q, int page, int size) {
return Page.empty();
}
};
}
}Load Balancing
Eureka + Spring Cloud LoadBalancer entegrasyonu ile tüm HTTP client'lar otomatik olarak load balanced çalışır. @FeignClient(name = "user-service") içindeki isim Eureka'daki servis adıdır. Feign, Eureka'dan tüm instance'ları alır ve round-robin ile dağıtır.
Kısacası: RestClient imperative projeler için modern ve temiz, WebClient reactive projeler için, OpenFeign ise en az kodla mikroservisler arası iletişim için idealdir.
AI Asistan
Sorularını yanıtlamaya hazır