404 Not Found

404 Not Found


nginx

برمجة Spring WebFl التفاعلية

WebFlux هو محرك Spring غير المتزامن—بفضل Mono الفردي وFlux من الدرجة الأولى، يتعامل الإدخال/الإخراج غير المحظور مع عشرات الآلاف من الاتصالات المتزامنة.

1. ما ستتعلمه


2. قصة حقيقية من مهندس التزامن العالي

(1) نقطة الألم: استنفاد تجمع الخيوط

واجهت OrderFlow الخاصة بـ Alice اختناقًا أثناء حدث مبيعات كبير—مع 500 مستخدم متزامن يستعلون عن الطلبات، تم استنفاد تجمع خيوط Tomcat (المعين إلى 200 خيط افتراضيًا)، وتم وضع الطلبات في قائمة الانتظار، وقفز زمن الاستجابة P99 إلى 3 ثوانٍ. معظم الوقت، كانت الخيوط تنتظر الإدخال/الإخراج (قاعدة بيانات، شبكة)، وكان استخدام المعالج 15% فقط. اقترح Bob زيادة عدد الخيوط، لكن إضافة 1,000 خيط ستستهلك 1 غيغابايت إضافي من الذاكرة.

(2) حل WebFlux

يتعامل WebFlux مع التزامن العالي بعدد قليل من الخيوط—لا تُحظر الخيوط أثناء انتظار الإدخال/الإخراج ويمكنها الاستمرار في خدمة الطلبات الأخرى:

JAVA
@GetMapping("/{id}")
public Mono<OrderResponse> getOrder(@PathVariable Long id) {
    return orderRepository.findById(id)
        .map(OrderResponse::from)
        .switchIfEmpty(Mono.error(new ResourceNotFoundException("Order", id)));
}

(3) الإيرادات

بعد أن أعادت Alice بناء واجهة برمجة استعلام الطلبات باستخدام WebFlux، أصبحت أربعة خيوط قادرة على التعامل مع 2,000 طلب متزامن، وانخفض استخدام الذاكرة بنسبة 70%، وانخفض زمن الاستجابة P99 من 3 ثوانٍ إلى 100 مللي ثانية، وارتفع استخدام المعالج إلى 60%.


3. المفاهيم الأساسية للبرمجة التفاعلية

(1) Mono وFlux

النوع المعنى التشبيه مثال
Mono<T> 0 أو 1 عنصر نسخة غير متزامنة من Optional استعلام طلب واحد
Flux<T> من 0 إلى N عنصر نسخة غير متزامنة من Stream استعلام قائمة الطلبات
Mono<Void> لا قيمة إرجاع إشارة اكتمال عملية حذف
100%
graph LR
    A["Publisher"] --> B["Mono<br/>0 أو 1 عنصر"]
    A --> C["Flux<br/>من 0 إلى N عنصر"]
    B --> D["onNext → onComplete"]
    C --> E["onNext x N → onComplete"]
    B --> F["onError"]
    C --> F

(1) ▶ مثال: العمليات الأساسية في Mono وFlux

JAVA
// Mono: قيمة واحدة
Mono<String> mono = Mono.just("Hello OrderFlow");
Mono<String> empty = Mono.empty();
Mono<String> fromCallable = Mono.fromCallable(() -> fetchOrder(1L));

// Flux: قيم متعددة
Flux<Integer> flux = Flux.just(1, 2, 3, 4, 5);
Flux<Integer> range = Flux.range(1, 100);
Flux<Long> interval = Flux.interval(Duration.ofSeconds(1));

الناتج:

TEXT
// التنفيذ ناجح

(2) العوامل الشائعة

العامل الوظيفة مثال
map تحويل العناصر .map(OrderResponse::from)
flatMap تحويل غير متزامن .flatMap(this::enrichOrder)
filter تصفية .filter(order -> "PENDING".equals(order.getStatus()))
switchIfEmpty بديل عند القيمة الفارغة .switchIfEmpty(Mono.error(...))
onErrorResume تخفيض في حالة الخطأ .onErrorResume(e -> fallbackOrder())
timeout التحكم في المهلة .timeout(Duration.ofSeconds(5))
retry إعادة المحاولة .retry(3)
subscribeOn تحديد خيط الاشتراك .subscribeOn(Schedulers.boundedElastic())

(2) ▶ مثال: تسلسل العوامل

JAVA
public Mono<OrderResponse> getOrderWithDetails(Long id) {
    return orderRepository.findById(id)
        .flatMap(order ->
            productRepository.findById(order.getProductId())
                .map(product -> OrderResponse.from(order, product)))
        .switchIfEmpty(Mono.error(
            new ResourceNotFoundException("Order", id)))
        .timeout(Duration.ofSeconds(3))
        .onErrorResume(TimeoutException.class,
            e -> Mono.error(new RuntimeException("انتهت مهلة استعلام الطلب")))
        .retry(2);
}

الناتج:

TEXT
// التنفيذ ناجح

4. واجهة برمجة التطبيقات REST في WebFlux

(1) ▶ مثال: المتحكم التفاعلي

JAVA
@RestController
@RequestMapping("/api/v1/reactive/orders")
public class ReactiveOrderController {

    private final ReactiveOrderService orderService;

    public ReactiveOrderController(ReactiveOrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/{id}")
    public Mono<OrderResponse> getOrder(@PathVariable Long id) {
        return orderService.findById(id);
    }

    @GetMapping
    public Flux<OrderResponse> listOrders(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        return orderService.findAll(page, size);
    }

    @PostMapping
    public Mono<ResponseEntity<OrderResponse>> createOrder(
            @Valid @RequestBody CreateOrderRequest request) {
        return orderService.create(request)
            .map(order -> ResponseEntity
                .status(HttpStatus.CREATED).body(order));
    }

    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> cancelOrder(@PathVariable Long id) {
        return orderService.cancel(id)
            .then(Mono.just(ResponseEntity.noContent().<Void>build()));
    }
}

الناتج:

TEXT
// التنفيذ ناجح
البُعد WebMVC WebFlux
نوع الإرجاع كائن متزامن Mono<T> / Flux<T>
نموذج الخيوط خيط واحد لكل طلب حلقة الأحداث (خيوط قليلة)
نموذج الإدخال/الإخراج محظور غير محظور
الحاوية Tomcat / Jetty Netty
حد التزامن حجم تجمع الخيوط غير محدود تقريبًا

5. الاختيار بين WebFlux وWebMVC

(1) قرار اختيار المنتج

السيناريو التوصية السبب
واجهة برمجة تطبيقات CRUD، تزامن < 500 WebMVC بسيط وبديهي، نظام بيئي ناضج
كثيف الإدخال/الإخراج، تزامن عالي WebFlux غير محظور، يدعم التزامن العالي بعدد قليل من الخيوط
تدفق في الوقت الفعلي (SSE/WebSocket) WebFlux دعم أصلي
عدد كبير من العمليات المحظورة (JDBC) WebMVC العمليات المحظورة أسوأ فعليًا في WebFlux
سيناريوهات مختلطة WebMVC + جزء غير متزامن تحسين تدريجي
⚠️ ملاحظة: WebFlux ليس حلًا سحريًا. إذا كان الكود يستخدم بشكل مكثف الإدخال/الإخراج المحظور (مثل JDBC)، فإن WebFlux سيعمل بشكل أسوأ من WebMVC فعليًا. اختيار WebFlux يعني أن سلسلة الاستدعاء بأكملها يجب أن تكون غير محظورة.

(1) ▶ مثال: أحداث إرسال الخادم (SSE)

JAVA
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<OrderEvent> streamOrderEvents() {
    return orderEventPublisher.eventStream()
        .log("order-events");
}

الناتج:

TEXT
// التنفيذ ناجح

6. Spring Data R2DBC

(1) الوصول التفاعلي إلى قاعدة البيانات

(1) ▶ مثال: كيان ومستودع R2DBC

XML
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-mysql</artifactId>
</dependency>

الناتج:

TEXT
// التنفيذ ناجح
JAVA
@Table("products")
public class Product {

    @Id
    private Long id;
    private String name;
    private BigDecimal price;
    private Integer stock;

    public Product() {}

    public Product(String name, BigDecimal price, Integer stock) {
        this.name = name;
        this.price = price;
        this.stock = stock;
    }
    // getters
}

public interface ReactiveProductRepository
        extends ReactiveCrudRepository<Product, Long> {

    Flux<Product> findByNameContaining(String keyword);

    Flux<Product> findByStockLessThan(Integer threshold);
}
البُعد JPA + JDBC R2DBC
نموذج المشغل محظور غير محظور
نمط واجهة برمجة التطبيقات متزامن تفاعلي (Mono/Flux)
تعيين العلاقات مدعوم (@OneToMany) غير مدعوم
لغة الاستعلام JPQL SQL أصلي / اشتقاق اسم الطريقة
المعاملات @Transactional @Transactional (تفاعلي)

(2) ▶ مثال: خدمة تفاعلية

JAVA
@Service
public class ReactiveOrderService {

    private final ReactiveOrderRepository orderRepository;
    private final ReactiveProductRepository productRepository;

    public ReactiveOrderService(ReactiveOrderRepository orderRepository,
                                ReactiveProductRepository productRepository) {
        this.orderRepository = orderRepository;
        this.productRepository = productRepository;
    }

    public Mono<OrderResponse> create(CreateOrderRequest request) {
        return productRepository.findById(request.productId())
            .switchIfEmpty(Mono.error(
                new ResourceNotFoundException("Product", request.productId())))
            .flatMap(product -> {
                if (product.getStock() < request.quantity()) {
                    return Mono.error(new InsufficientStockException(
                        product.getId(), product.getStock(), request.quantity()));
                }
                product.deductStock(request.quantity());
                return productRepository.save(product)
                    .then(orderRepository.save(new Order(product.getId(), request.quantity())));
            })
            .map(OrderResponse::from);
    }

    public Flux<OrderResponse> findAll(int page, int size) {
        return orderRepository.findAll()
            .skip(page * size)
            .take(size)
            .map(OrderResponse::from);
    }

    public Mono<Void> cancel(Long orderId) {
        return orderRepository.findById(orderId)
            .flatMap(order -> {
                order.setStatus("CANCELLED");
                return orderRepository.save(order);
            })
            .then();
    }
}

الناتج:

TEXT
// التنفيذ ناجح

7. مثال شامل: واجهة برمجة استعلام الطلبات التفاعلية OrderFlow

JAVA
// ReactiveProductRepository.java
public interface ReactiveProductRepository
        extends ReactiveCrudRepository<Product, Long> {
    Flux<Product> findByNameContaining(String keyword);
}

// ReactiveOrderRepository.java
public interface ReactiveOrderRepository
        extends ReactiveCrudRepository<Order, Long> {
    Flux<Order> findByStatus(String status);
}

// ReactiveOrderController.java
@RestController
@RequestMapping("/api/v1/reactive/orders")
public class ReactiveOrderController {

    private final ReactiveOrderService orderService;

    public ReactiveOrderController(ReactiveOrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/{id}")
    public Mono<OrderResponse> get(@PathVariable Long id) {
        return orderService.findById(id);
    }

    @GetMapping
    public Flux<OrderResponse> list(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        return orderService.findAll(page, size);
    }

    @GetMapping("/status/{status}")
    public Flux<OrderResponse> byStatus(@PathVariable String status) {
        return orderService.findByStatus(status);
    }

    @PostMapping
    public Mono<ResponseEntity<OrderResponse>> create(
            @RequestBody CreateOrderRequest req) {
        return orderService.create(req)
            .map(o -> ResponseEntity.status(HttpStatus.CREATED).body(o));
    }

    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> cancel(@PathVariable Long id) {
        return orderService.cancel(id)
            .thenReturn(ResponseEntity.noContent().<Void>build());
    }
}

// ReactiveOrderService.java
@Service
public class ReactiveOrderService {

    private final ReactiveOrderRepository orderRepo;
    private final ReactiveProductRepository productRepo;

    public ReactiveOrderService(ReactiveOrderRepository orderRepo,
                                ReactiveProductRepository productRepo) {
        this.orderRepo = orderRepo;
        this.productRepo = productRepo;
    }

    public Mono<OrderResponse> findById(Long id) {
        return orderRepo.findById(id)
            .map(OrderResponse::from)
            .switchIfEmpty(Mono.error(new ResourceNotFoundException("Order", id)));
    }

    public Flux<OrderResponse> findAll(int page, int size) {
        return orderRepo.findAll().skip((long) page * size).take(size)
            .map(OrderResponse::from);
    }

    public Flux<OrderResponse> findByStatus(String status) {
        return orderRepo.findByStatus(status).map(OrderResponse::from);
    }

    public Mono<OrderResponse> create(CreateOrderRequest req) {
        return productRepo.findById(req.productId())
            .switchIfEmpty(Mono.error(new ResourceNotFoundException("Product", req.productId())))
            .flatMap(p -> {
                if (p.getStock() < req.quantity()) {
                    return Mono.error(new InsufficientStockException(p.getId(), p.getStock(), req.quantity()));
                }
                p.deductStock(req.quantity());
                return productRepo.save(p)
                    .then(orderRepo.save(new Order(p.getId(), req.quantity())));
            })
            .map(OrderResponse::from);
    }

    public Mono<Void> cancel(Long id) {
        return orderRepo.findById(id)
            .flatMap(o -> { o.setStatus("CANCELLED"); return orderRepo.save(o); })
            .then();
    }
}

❓ أسئلة شائعة

س هل يمكن لـ WebFlux أن يحل محل WebMVC بالكامل؟
ج لا نوصي بذلك. النظام البيئي لـ WebMVC أكثر نضجًا، وبالنسبة لغالبية مشاريع CRUD، فإن WebMVC أبسط في الاستخدام. WebFlux مناسب لسيناريوهات الإدخال/الإخراج المكثف والتزامن العالي والتدفق في الوقت الفعلي. يمكن للاثنين التعايش، لكن يجب عدم خلطهما (لا تستخدم كودًا محظورًا داخل WebFlux).
س ما الفرق بين Mono وCompletableFuture؟
ج Mono كسول (ينفذ فقط عند الاشتراك)، بينما CompletableFuture نهم (يبدأ التنفيذ بمجرد إنشائه). Mono يدعم الضغط العكسي والإلغاء، بينما CompletableFuture لا يدعم ذلك.
س هل يدعم R2DBC تعيين العلاقات @OneToMany؟
ج لا، لا يدعم. R2DBC هو مشغل قاعدة بيانات تفاعلي خفيف لا يقوم بتعيين العلاقات. عند الحاجة إلى استعلامات الانضمام، يجب دمج استعلامات متعددة يدويًا باستخدام flatMap.
س كيف أستدعي كودًا محظورًا في WebFlux؟
ج استخدم Mono.fromCallable() + subscribeOn(Schedulers.boundedElastic()) لجدولة العمليات المحظورة إلى تجمع الخيوط المرن. ومع ذلك، الاستخدام المتكرر يشير إلى أن هذا النهج غير مناسب لـ WebFlux.
س كيف يتم التعامل مع الأخطاء في WebFlux؟
ج 1) العوامل onErrorResume / onErrorReturn؛ 2) @ExceptionHandler العام (مدعوم أيضًا من WebFlux)؛ 3) onErrorMap لتحويل أنواع الاستثناءات.
س كيف تختبر WebFlux؟
ج استخدم WebTestClient بدلاً من MockMvc. يقوم @WebFluxTest بتحميل طبقة WebFlux. يُستخدم StepVerifier لاختبار تسلسلات العناصر في Mono/Flux.

📖 ملخص


📝 تمارين

  1. مسألة أساسية (الصعوبة ⭐): نفذ واجهة برمجة تطبيقات تفاعلية لاستعلام المنتجات باستخدام WebFlux (GET /api/v1/reactive/products/{id})، مع الاتصال بقاعدة بيانات H2 في الذاكرة عبر R2DBC.

  2. تمرين متقدم (الصعوبة: ⭐⭐): نفذ واجهة برمجة تطبيقات تفاعلية لوضع الطلبات تغطي العملية بالكامل—بما في ذلك فحص المخزون وخصم المخزون وإنشاء الطلب—باستخدام flatMap لدمج عمليات تفاعلية متعددة.

  3. تحدٍ (الصعوبة: ⭐⭐⭐): استخدم WebTestClient وStepVerifier لكتابة اختبار لواجهة برمجة تطبيقات تفاعلية، ونفذ واجهة دفع أحداث الطلبات في الوقت الفعلي SSE، وقارن اختلافات الأداء بين WebMVC وWebFlux تحت 1,000 اتصال متزامن.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%