انتقل إلى المحتوى الرئيسي

الفصل 5: الاتصال بين الخدمات

🎯 أهداف الفصل​

  • فهم أوضاع الاتصال المتزامن مقابل غير المتزامن
  • تنفيذ استدعاءات REST باستخدام OpenFeign
  • تطبيق أنماط المرونة باستخدام Resilience4j
  • إتقان Circuit Breaker، Retry، Rate Limiter
  • تنفيذ fallbacks فعالة

1. أوضاع الاتصال​

1.1 الاتصال المتزامن​

الخصائص:

  • العميل ينتظر الرد
  • سهل التنفيذ
  • اقتران زمني (اعتماد على التوافر)
  • زمن استجابة متراكم

1.2 الاتصال غير المتزامن​

الخصائص:

  • إرسال ونسيان
  • فك الارتباط الزمني
  • أكثر مرونة
  • أكثر تعقيدًا (اتساق نهائي)

1.3 متى تستخدم ماذا؟​

حالة الاستخداممتزامنغير متزامن
التحقق من المستخدم✅
الحصول على تفاصيل المنتج✅
إنشاء طلب✅
إرسال إشعار✅
تحديث التحليلات✅
مزامنة البيانات✅

2. OpenFeign - عميل HTTP إعلاني​

2.1 ما هو Feign؟​

Feign (الآن OpenFeign) هو عميل HTTP إعلاني يسمح بتعريف استدعاءات REST عبر واجهات Java.

// بدون Feign (RestTemplate)
String url = "http://user-service/api/users/" + userId;
User user = restTemplate.getForObject(url, User.class);

// مع Feign (إعلاني)
User user = userClient.getUserById(userId);

2.2 تبعيات Maven​

<dependencies>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- Eureka Client (للاكتشاف) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- Resilience4j -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
</dependencies>

2.3 تفعيل Feign​

package com.example.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients // تفعيل Feign
public class OrderServiceApplication {

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

2.4 تعريف عميل Feign​

package com.example.orderservice.client;

import com.example.orderservice.dto.UserDto;
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") // اسم الخدمة في Eureka
public interface UserClient {

@GetMapping("/api/users/{id}")
UserDto getUserById(@PathVariable("id") Long id);

@GetMapping("/api/users")
List<UserDto> getAllUsers();

@GetMapping("/api/users/{id}/exists")
Boolean userExists(@PathVariable("id") Long id);
}

2.5 DTO​

package com.example.orderservice.dto;

public class UserDto {
private Long id;
private String username;
private String email;
private String firstName;
private String lastName;

// Constructors
public UserDto() {}

public UserDto(Long id, String username, String email,
String firstName, String lastName) {
this.id = id;
this.username = username;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}

// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }

public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }

public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }

public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}

2.6 الاستخدام​

package com.example.orderservice.service;

import com.example.orderservice.client.UserClient;
import com.example.orderservice.client.ProductClient;
import com.example.orderservice.dto.UserDto;
import com.example.orderservice.dto.ProductDto;
import com.example.orderservice.model.Order;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

private final UserClient userClient;
private final ProductClient productClient;
private final OrderRepository orderRepository;

public OrderService(UserClient userClient,
ProductClient productClient,
OrderRepository orderRepository) {
this.userClient = userClient;
this.productClient = productClient;
this.orderRepository = orderRepository;
}

public Order createOrder(Long userId, Long productId, int quantity) {
// استدعاء متزامن لخدمة المستخدم
UserDto user = userClient.getUserById(userId);
if (user == null) {
throw new RuntimeException("User not found: " + userId);
}

// استدعاء متزامن لخدمة المنتج
ProductDto product = productClient.getProductById(productId);
if (product == null) {
throw new RuntimeException("Product not found: " + productId);
}

// إنشاء الطلب
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setTotalPrice(product.getPrice() * quantity);
order.setUserEmail(user.getEmail());

return orderRepository.save(order);
}
}

2.7 تكوين Feign​

spring:
cloud:
openfeign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
loggerLevel: BASIC
user-service:
connectTimeout: 3000
readTimeout: 5000
loggerLevel: FULL

# Logging Feign
logging:
level:
com.example.orderservice.client: DEBUG

2.8 مستويات تسجيل Feign​

المستوىالوصف
NONEلا يوجد تسجيل
BASICالطريقة، URL، الحالة، الوقت
HEADERS+ الرؤوس
FULL+ جسم الطلب/الاستجابة

3. Resilience4j - أنماط المرونة​

3.1 لماذا المرونة؟​

3.2 التبعيات​

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>

<!-- إذا كنت تريد التعليقات التوضيحية @CircuitBreaker، @Retry، إلخ. -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>

<!-- AOP للتعليقات التوضيحية -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

4. Circuit Breaker Pattern​

4.1 حالات الدائرة​

4.2 تكوين YAML​

resilience4j:
circuitbreaker:
configs:
default:
# حجم النافذة المنزلقة
slidingWindowSize: 10
# نوع النافذة (COUNT_BASED أو TIME_BASED)
slidingWindowType: COUNT_BASED
# الحد الأدنى لعدد المكالمات قبل الحساب
minimumNumberOfCalls: 5
# عتبة الفشل لفتح (%)
failureRateThreshold: 50
# مدة في حالة OPEN (ms)
waitDurationInOpenState: 10000
# المكالمات المسموح بها في HALF_OPEN
permittedNumberOfCallsInHalfOpenState: 3
# الانتقال التلقائي من OPEN إلى HALF_OPEN
automaticTransitionFromOpenToHalfOpenEnabled: true
# الاستثناءات التي تعتبر فشلًا
recordExceptions:
- java.io.IOException
- java.util.concurrent.TimeoutException
- feign.FeignException
# الاستثناءات التي يتم تجاهلها
ignoreExceptions:
- com.example.BusinessException

instances:
userService:
baseConfig: default
productService:
baseConfig: default
failureRateThreshold: 30

4.3 مع التعليقات التوضيحية​

package com.example.orderservice.service;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;

@Service
public class UserServiceClient {

private final UserClient userClient;

public UserServiceClient(UserClient userClient) {
this.userClient = userClient;
}

@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
public UserDto getUser(Long userId) {
return userClient.getUserById(userId);
}

// Fallback - يجب أن يكون له نفس التوقيع + استثناء
public UserDto getUserFallback(Long userId, Exception ex) {
// سجل الخطأ
log.warn("Circuit breaker triggered for user {}. Error: {}",
userId, ex.getMessage());

// إرجاع قيمة افتراضية أو مخزنة
return new UserDto(userId, "unknown", "unknown@example.com",
"Unknown", "User");
}
}

4.4 مع Feign (Fallback Factory)​

package com.example.orderservice.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

@FeignClient(
name = "user-service",
fallbackFactory = UserClientFallbackFactory.class
)
public interface UserClient {

@GetMapping("/api/users/{id}")
UserDto getUserById(@PathVariable("id") Long id);

@GetMapping("/api/users/{id}/exists")
Boolean userExists(@PathVariable("id") Long id);
}
package com.example.orderservice.client;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

private static final Logger log = LoggerFactory.getLogger(UserClientFallbackFactory.class);

@Override
public UserClient create(Throwable cause) {
log.error("UserClient fallback triggered. Cause: {}", cause.getMessage());

return new UserClient() {
@Override
public UserDto getUserById(Long id) {
return new UserDto(id, "fallback-user", "fallback@example.com",
"Fallback", "User");
}

@Override
public Boolean userExists(Long id) {
return false; // متشائم في حالة الخطأ
}
};
}
}

تفعيل fallbacks Feign:

spring:
cloud:
openfeign:
circuitbreaker:
enabled: true

5. Retry Pattern​

5.1 التكوين​

resilience4j:
retry:
configs:
default:
maxAttempts: 3
waitDuration: 1000
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2
retryExceptions:
- java.io.IOException
- java.util.concurrent.TimeoutException
- feign.RetryableException
ignoreExceptions:
- com.example.BusinessException

instances:
userService:
baseConfig: default
maxAttempts: 5

5.2 مع التعليقات التوضيحية​

@Service
public class ProductServiceClient {

@Retry(name = "productService", fallbackMethod = "getProductFallback")
public ProductDto getProduct(Long productId) {
return productClient.getProductById(productId);
}

public ProductDto getProductFallback(Long productId, Exception ex) {
log.warn("Retry exhausted for product {}. Error: {}",
productId, ex.getMessage());
throw new ServiceUnavailableException("Product service unavailable");
}
}

5.3 دمج Retry و Circuit Breaker​

@Service
public class UserServiceClient {

// الترتيب مهم! Retry ثم CircuitBreaker
@Retry(name = "userService")
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
public UserDto getUser(Long userId) {
return userClient.getUserById(userId);
}

public UserDto getUserFallback(Long userId, Exception ex) {
return getCachedUser(userId);
}
}

ترتيب التنفيذ:

  1. الاستدعاء الأولي
  2. إذا فشل → Retry (حتى maxAttempts)
  3. إذا فشلت جميع المحاولات → Circuit Breaker
  4. إذا كان Circuit OPEN → Fallback فوري

6. Rate Limiter Pattern​

6.1 التكوين​

resilience4j:
ratelimiter:
configs:
default:
# عدد الأذونات لكل فترة
limitForPeriod: 10
# مدة الفترة
limitRefreshPeriod: 1s
# مهلة الانتظار للحصول على إذن
timeoutDuration: 500ms

instances:
userService:
baseConfig: default
limitForPeriod: 100
limitRefreshPeriod: 1s

6.2 مع التعليقات التوضيحية​

@Service
public class ExternalApiClient {

@RateLimiter(name = "externalApi", fallbackMethod = "rateLimitFallback")
public Response callExternalApi(Request request) {
return externalClient.call(request);
}

public Response rateLimitFallback(Request request, RequestNotPermitted ex) {
log.warn("Rate limit exceeded");
throw new TooManyRequestsException("Rate limit exceeded. Try again later.");
}
}

7. Bulkhead Pattern​

7.1 المفهوم​

يقوم Bulkhead بعزل الموارد لمنع خدمة بطيئة من حظر الكل.

7.2 التكوين​

resilience4j:
bulkhead:
configs:
default:
maxConcurrentCalls: 10
maxWaitDuration: 500ms

instances:
userService:
baseConfig: default
maxConcurrentCalls: 5

thread-pool-bulkhead:
configs:
default:
maxThreadPoolSize: 10
coreThreadPoolSize: 5
queueCapacity: 20

instances:
productService:
baseConfig: default

7.3 مع التعليقات التوضيحية​

@Service
public class OrderService {

@Bulkhead(name = "userService", type = Bulkhead.Type.SEMAPHORE)
public UserDto getUser(Long userId) {
return userClient.getUserById(userId);
}

@Bulkhead(name = "productService", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<ProductDto> getProductAsync(Long productId) {
return CompletableFuture.supplyAsync(() ->
productClient.getProductById(productId)
);
}
}

8. Time Limiter Pattern​

8.1 التكوين​

resilience4j:
timelimiter:
configs:
default:
timeoutDuration: 5s
cancelRunningFuture: true

instances:
userService:
baseConfig: default
timeoutDuration: 3s

8.2 مع التعليقات التوضيحية​

@Service
public class SlowServiceClient {

@TimeLimiter(name = "slowService", fallbackMethod = "timeoutFallback")
public CompletableFuture<Response> callSlowService() {
return CompletableFuture.supplyAsync(() -> {
// استدعاء بطيء محتمل
return slowClient.call();
});
}

public CompletableFuture<Response> timeoutFallback(TimeoutException ex) {
return CompletableFuture.completedFuture(Response.timeout());
}
}

9. دمج جميع الأنماط​

9.1 التكوين الكامل​

resilience4j:
circuitbreaker:
instances:
userService:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 10000
permittedNumberOfCallsInHalfOpenState: 3

retry:
instances:
userService:
maxAttempts: 3
waitDuration: 500ms
enableExponentialBackoff: true

ratelimiter:
instances:
userService:
limitForPeriod: 100
limitRefreshPeriod: 1s

bulkhead:
instances:
userService:
maxConcurrentCalls: 10

timelimiter:
instances:
userService:
timeoutDuration: 3s

9.2 خدمة مع جميع الأنماط​

@Service
@Slf4j
public class ResilientUserService {

private final UserClient userClient;

public ResilientUserService(UserClient userClient) {
this.userClient = userClient;
}

// ترتيب التنفيذ: Bulkhead → TimeLimiter → RateLimiter → CircuitBreaker → Retry
@Bulkhead(name = "userService")
@TimeLimiter(name = "userService")
@RateLimiter(name = "userService")
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
@Retry(name = "userService")
public CompletableFuture<UserDto> getUser(Long userId) {
return CompletableFuture.supplyAsync(() -> {
log.info("Calling user service for user: {}", userId);
return userClient.getUserById(userId);
});
}

public CompletableFuture<UserDto> getUserFallback(Long userId, Exception ex) {
log.warn("Fallback triggered for user: {}. Error: {}",
userId, ex.getMessage());
return CompletableFuture.completedFuture(
new UserDto(userId, "fallback", "fallback@example.com", "Fallback", "User")
);
}
}

10. مراقبة Resilience4j​

10.1 نقاط النهاية Actuator​

management:
endpoints:
web:
exposure:
include: health,circuitbreakers,circuitbreakerevents,retries,ratelimiters
endpoint:
health:
show-details: always
health:
circuitbreakers:
enabled: true
ratelimiters:
enabled: true

10.2 نقاط النهاية المتاحة​

# حالة circuit breakers
curl http://localhost:8083/actuator/circuitbreakers

# أحداث circuit breakers
curl http://localhost:8083/actuator/circuitbreakerevents

# مقاييس Prometheus
curl http://localhost:8083/actuator/prometheus | grep resilience4j

10.3 مثال على الاستجابة​

{
"circuitBreakers": {
"userService": {
"failureRate": "20.0%",
"slowCallRate": "0.0%",
"state": "CLOSED",
"bufferedCalls": 10,
"failedCalls": 2,
"slowCalls": 0,
"notPermittedCalls": 0
}
}
}

11. ملخص​

جدول ملخص​

النمطالمشكلة المحلولةمتى تستخدمه
Circuit Breakerسلسلة من الأعطالاستدعاءات خارجية غير مستقرة
Retryأخطاء مؤقتةمهلات الشبكة، 503
Rate Limiterالتحميل الزائدواجهات برمجة التطبيقات الخارجية مع الحصص
Bulkheadاستنزاف المواردعزل الخدمات البطيئة
Time Limiterاستدعاءات محظورةخدمات بطيئة محتملة

ترتيب الأنماط​

Request → Bulkhead → TimeLimiter → RateLimiter → CircuitBreaker → Retry → Service

التكوين الموصى به للإنتاج​

resilience4j:
circuitbreaker:
instances:
default:
slidingWindowSize: 100
failureRateThreshold: 50
waitDurationInOpenState: 30000
permittedNumberOfCallsInHalfOpenState: 10

retry:
instances:
default:
maxAttempts: 3
waitDuration: 1000ms
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2

ratelimiter:
instances:
default:
limitForPeriod: 1000
limitRefreshPeriod: 1s

12. اختبار التحقق​

السؤال 1​

ما الفرق بين @FeignClient(fallback) و @FeignClient(fallbackFactory)؟

عرض الإجابة
  • fallback: فئة ثابتة، لا يمكن الوصول إلى الاستثناء
  • fallbackFactory: يتلقى الاستثناء كمعامل، يسمح بتسجيل أو تغيير fallback حسب الخطأ

السؤال 2​

ما هو ترتيب تنفيذ Retry و CircuitBreaker؟

عرض الإجابة

Retry يتم تنفيذه أولاً (أقرب إلى الخدمة)، ثم CircuitBreaker.

إذا فشلت جميع المحاولات، فإن CircuitBreaker يسجل فشلًا. إذا كان CircuitBreaker مفتوحًا، فلن يتم حتى محاولة retries.


الانتقال إلى الأعمال العملية →

العودة إلى الوحدة 13