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

اختبار: تقنيات متقدمة

اختبر معرفتك حول WebFlux، Redis، RabbitMQ، الجدولة وWebSockets.

30 سؤالًا - انقر على كل سؤال لكشف الإجابة.


الأسئلة 1-10: البرمجة التفاعلية (WebFlux)​

السؤال 1: ما الفرق بين Spring MVC وWebFlux؟

الإجابة:

Spring MVCWebFlux
حجبغير حجب
1 خيط/طلبحلقة الأحداث
Servlet APIReactive Streams
Tomcat, JettyNetty
قابلية التوسع العموديةقابلية التوسع الأفقية

السؤال 2: ما الفرق بين Mono وFlux؟

الإجابة:

  • Mono: 0 أو 1 عنصر
  • Flux: 0 إلى N عناصر
Mono<User> user = userRepository.findById(1L);
Flux<User> users = userRepository.findAll();

السؤال 3: ما هي المشغلين الشائعة لـ Mono/Flux؟

الإجابة:

المشغلالوصف
mapتحويل متزامن
flatMapتحويل غير متزامن
filterتصفية
zipدمج
onErrorReturnاسترجاع عند الخطأ
switchIfEmptyاسترجاع عند الفراغ
timeoutحد زمني
retryإعادة المحاولة

السؤال 4: كيف تكتب وحدة تحكم تفاعلية؟

الإجابة:

@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserRepository repository;

@GetMapping
public Flux<User> getAll() {
return repository.findAll();
}

@GetMapping("/{id}")
public Mono<ResponseEntity<User>> getById(@PathVariable Long id) {
return repository.findById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<User> create(@RequestBody User user) {
return repository.save(user);
}
}

السؤال 5: ما هو R2DBC؟

الإجابة: Reactive Relational Database Connectivity - الوصول غير الحاجب إلى قواعد البيانات العلائقية.

public interface UserRepository extends ReactiveCrudRepository<User, Long> {
Flux<User> findByEmail(String email);
}

يستبدل JPA/JDBC الحاجب في التطبيقات التفاعلية.


السؤال 6: كيف تستخدم WebClient؟

الإجابة:

@Service
public class ExternalApiService {

private final WebClient webClient;

public Mono<Data> fetchData(String id) {
return webClient.get()
.uri("/data/{id}", id)
.retrieve()
.bodyToMono(Data.class)
.timeout(Duration.ofSeconds(5))
.retry(3);
}
}

السؤال 7: لماذا لا يجب أبدًا استدعاء block() في الإنتاج؟

الإجابة: block() يحجب الخيط، مما يلغي فوائد التفاعلية:

  • يحجب حلقة الأحداث
  • يقلل من الإنتاجية
  • يمكن أن يسبب في حالات توقف تام

يستخدم فقط في الاختبارات.


السؤال 8: كيف تختبر الكود التفاعلي؟

الإجابة:

@Test
void shouldFindUser() {
Mono<User> userMono = userService.findById(1L);

StepVerifier.create(userMono)
.assertNext(user -> {
assertThat(user.getName()).isEqualTo("John");
})
.verifyComplete();
}

@Test
void shouldHandleError() {
Mono<User> userMono = userService.findById(999L);

StepVerifier.create(userMono)
.expectError(NotFoundException.class)
.verify();
}

السؤال 9: متى تستخدم WebFlux؟

الإجابة:

  • التزامن العالي (آلاف الاتصالات)
  • تدفق البيانات
  • التطبيقات في الوقت الحقيقي
  • التكاملات مع الكثير من الاستدعاءات الخارجية

تجنب إذا: الفريق غير مدرب، المكتبات الحاجبة، CRUD بسيط.


السؤال 10: كيف تدير الأخطاء في التفاعلية؟

الإجابة:

return userRepository.findById(id)
.switchIfEmpty(Mono.error(new NotFoundException("User not found")))
.onErrorReturn(new User("Fallback"))
.onErrorResume(e -> {
log.error("Error: {}", e.getMessage());
return Mono.empty();
});

الأسئلة 11-17: التخزين المؤقت مع Redis​

السؤال 11: كيف تقوم بتكوين Spring Cache مع Redis؟

الإجابة:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@Configuration
@EnableCaching
public class CacheConfig { }

السؤال 12: كيف تستخدم @Cacheable؟

الإجابة:

@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
log.info("Fetching from DB");
return userRepository.findById(id).orElseThrow();
}

// أول استدعاء: قاعدة البيانات
// الاستدعاءات التالية: التخزين المؤقت

السؤال 13: ما الفرق بين @Cacheable, @CachePut و@CacheEvict؟

الإجابة:

التعليق التوضيحيالسلوك
@Cacheableيقرأ من التخزين المؤقت أو ينفذ ويخزن
@CachePutينفذ دائمًا ويحدث التخزين المؤقت
@CacheEvictيحذف من التخزين المؤقت
@CachePut(value = "users", key = "#user.id")
public User update(User user) { }

@CacheEvict(value = "users", key = "#id")
public void delete(Long id) { }

@CacheEvict(value = "users", allEntries = true)
public void clearCache() { }

السؤال 14: كيف تقوم بتكوين TTL للتخزين المؤقت؟

الإجابة:

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues();

return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}

السؤال 15: كيف تستخدم RedisTemplate مباشرة؟

الإجابة:

@Service
public class SessionService {

private final RedisTemplate<String, Object> redisTemplate;

public void saveSession(String token, UserSession session) {
redisTemplate.opsForValue().set(
"session:" + token,
session,
Duration.ofHours(1)
);
}

public UserSession getSession(String token) {
return (UserSession) redisTemplate.opsForValue()
.get("session:" + token);
}
}

السؤال 16: كيف تدير عدم توفر Redis؟

الإجابة:

@Configuration
public class CacheConfig extends CachingConfigurerSupport {

@Override
public CacheErrorHandler errorHandler() {
return new SimpleCacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException ex, Cache cache, Object key) {
log.warn("Cache error: {}", ex.getMessage());
// الاستمرار بدون التخزين المؤقت
}
};
}
}

السؤال 17: متى تقوم بالتخزين المؤقت؟

الإجابة:

  • البيانات التي تقرأ بشكل متكرر وتعدل نادرًا
  • الحسابات المكلفة
  • استدعاءات API الخارجية
  • بيانات مرجعية (الدول، الفئات)

تجنب: البيانات في الوقت الحقيقي، البيانات الشخصية الحساسة.


الأسئلة 18-23: المراسلة مع RabbitMQ​

السؤال 18: كيف تقوم بتكوين RabbitMQ مع Spring؟

الإجابة:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

السؤال 19: ما هي أنواع التبادل في RabbitMQ؟

الإجابة:

النوعالتوجيه
Directمفتاح التوجيه الدقيق
Fanoutبث لجميع الطوابير
Topicمطابقة النمط (*.error, order.#)
Headersبناءً على الرؤوس

السؤال 20: كيف ترسل رسالة؟

الإجابة:

@Service
public class OrderProducer {

private final RabbitTemplate rabbitTemplate;

public void sendOrderEvent(OrderEvent event) {
rabbitTemplate.convertAndSend(
"order.exchange", // exchange
"order.created", // routing key
event // message
);
}
}

السؤال 21: كيف تستقبل رسالة؟

الإجابة:

@Component
public class OrderListener {

@RabbitListener(queues = "order.queue")
public void handleOrder(OrderEvent event) {
log.info("Received: {}", event);
processOrder(event);
}
}

السؤال 22: ما هي Dead Letter Queue؟

الإجابة: طابور يستقبل الرسائل الفاشلة (المرفوضة، المنتهية، الحد الأقصى للمحاولات):

@Bean
public Queue orderQueue() {
return QueueBuilder.durable("order.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange")
.withArgument("x-dead-letter-routing-key", "dlq")
.build();
}

يسمح بتحليل وإعادة معالجة الأخطاء.


السؤال 23: كيف تضمن تسليم الرسائل؟

الإجابة:

  1. Publisher confirms: تأكيد التبادل
  2. Consumer acks: تأكيد المعالجة
  3. Persistent messages: البقاء بعد إعادة التشغيل
  4. Durable queues: الطوابير الدائمة
@RabbitListener(queues = "order.queue", ackMode = "MANUAL")
public void handle(OrderEvent event, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
try {
process(event);
channel.basicAck(tag, false);
} catch (Exception e) {
channel.basicNack(tag, false, false);
}
}

الأسئلة 24-27: المهام المجدولة​

السؤال 24: كيف تقوم بتفعيل الجدولة؟

الإجابة:

@SpringBootApplication
@EnableScheduling
public class Application { }

السؤال 25: ما هي خيارات @Scheduled؟

الإجابة:

// كل 5 ثواني
@Scheduled(fixedRate = 5000)

// 5 ثواني بعد الانتهاء
@Scheduled(fixedDelay = 5000)

// تعبير Cron
@Scheduled(cron = "0 0 6 * * *") // كل يوم في الساعة 6

// مع تأخير ابتدائي
@Scheduled(fixedRate = 5000, initialDelay = 10000)

السؤال 26: كيف تكتب تعبير Cron؟

الإجابة:

┌───────────── الثانية (0-59)
│ ┌───────────── الدقيقة (0-59)
│ │ ┌───────────── الساعة (0-23)
│ │ │ ┌───────────── اليوم من الشهر (1-31)
│ │ │ │ ┌───────────── الشهر (1-12)
│ │ │ │ │ ┌───────────── اليوم من الأسبوع (0-7)
* * * * * *

أمثلة:

  • 0 0 * * * *: كل ساعة
  • 0 0 9 * * MON-FRI: الساعة 9 صباحًا في أيام الأسبوع
  • 0 */15 * * * *: كل 15 دقيقة

السؤال 27: كيف تنفذ طريقة بشكل غير متزامن؟

الإجابة:

@EnableAsync
@Configuration
public class AsyncConfig { }

@Service
public class EmailService {

@Async
public CompletableFuture<Boolean> sendEmail(String to) {
// يتم التنفيذ في خيط آخر
return CompletableFuture.completedFuture(true);
}
}

الأسئلة 28-30: WebSockets​

السؤال 28: كيف تقوم بتكوين WebSocket مع STOMP؟

الإجابة:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
}

السؤال 29: كيف ترسل وتستقبل رسائل WebSocket؟

الإجابة:

@Controller
public class ChatController {

@MessageMapping("/chat.send")
@SendTo("/topic/messages")
public ChatMessage send(ChatMessage message) {
return message;
}
}

// إرسال برمجي
@Service
public class NotificationService {

private final SimpMessagingTemplate template;

public void notifyUser(String userId, Notification notif) {
template.convertAndSendToUser(userId, "/queue/notifications", notif);
}
}

السؤال 30: متى تستخدم WebSocket مقابل REST؟

الإجابة:

WebSocketREST
اتصال ثنائي الاتجاهطلب-استجابة
في الوقت الحقيقيبيانات ثابتة
الدردشة، الإشعاراتCRUD، API
اتصال دائماتصالات قصيرة
الألعاب، التداولالتطبيقات التقليدية

النتيجة​

النتيجةالمستوى
27-30ممتاز - تقنيات متقدمة متقنة
21-26جيد - بعض المراجعات
15-20متوسط - أعد قراءة الفصول
أقل من 15بحاجة إلى مراجعة

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