NestJS — Node.js للمؤسسات

أنماط الرسائل والأحداث

16 دقيقة الدرس 37 من 48

أنماط الرسائل والأحداث

تتواصل الخدمات المصغّرة في NestJS عبر نمطين أساسيين: الطلب-الاستجابة (الرسائل) والإطلاق والنسيان (الأحداث). يُحدّد الاختيار بينهما طريقة عزل الخدمات، وكيفية انتشار الأخطاء، وما إذا كان المستدعي يتوقّف منتظرًا ردًّا. إتقان كلا النمطين — والمزخرفات وأساليب الـ proxy التي تُطبّقهما — هو العمود الفقري لأي بنية خدمات مصغّرة في NestJS.

@MessagePattern — الطلب-الاستجابة

يُشارك معالج مُزخرف بـ @MessagePattern في تبادل طلب-استجابة. يُرسل المستدعي رسالة وينتظر الرد. يُشبه هذا استدعاء HTTP التقليدي: يتوقّف العميل (أو يُكمل await على Promise) حتى يُعيد المعالج البعيد قيمة أو يرمي خطأً.

// orders.controller.ts — جانب الخدمة المصغّرة import { Controller } from '@nestjs/common'; import { MessagePattern, Payload } from '@nestjs/microservices'; @Controller() export class OrdersController { @MessagePattern({ cmd: 'get_order' }) getOrder(@Payload() data: { id: number }) { // إعادة قيمة يُرسل الرد إلى المستدعي return { id: data.id, status: 'confirmed', total: 99.99 }; } }

كائن النمط ({ cmd: 'get_order' }) هو مفتاح التوجيه. يمكن أن يكون نصًّا حرفيًّا أو أي كائن قابل للتسلسل، بشرط اتّفاق الطرفين على نفس القيمة.

@EventPattern — الإطلاق والنسيان

يستقبل معالج مُزخرف بـ @EventPattern الأحداث. لا ينتظر الناشر ردًّا — يُطلق الحدث ويمضي فورًا. هذا مثالي للآثار الجانبية كإرسال رسائل بريد إلكتروني، أو تحديث نماذج القراءة، أو كتابة سجلات التدقيق، حيث لا يهتم الناشر بالنتيجة.

// notifications.controller.ts — جانب الخدمة المصغّرة import { Controller } from '@nestjs/common'; import { EventPattern, Payload } from '@nestjs/microservices'; @Controller() export class NotificationsController { @EventPattern('order.placed') handleOrderPlaced(@Payload() data: { orderId: number; email: string }) { // لا قيمة مُعادة — الناشر لا ينتظر هذا أبدًا console.log(`Sending confirmation email to ${data.email}`); } }
تُهمَل قيم الإعادة من معالجات @EventPattern بصمت. لا تُوجّهها طبقة النقل إلى أي مكان. إن أعدت شيئًا مهمًّا من معالج حدث عن طريق الخطأ، فإنه يختفي. استخدم @MessagePattern فقط حين يحتاج المستدعي إلى نتيجة.

ClientProxy — الإرسال من المستدعي

على جانب المستدعي، أدخِل ClientProxy (المُنتَج بواسطة ClientsModule.register) واختر الأسلوب المناسب:

  • client.send(pattern, payload) — لـ @MessagePattern؛ يُعيد Observable يحلّ بالرد.
  • client.emit(pattern, payload) — لـ @EventPattern؛ يُعيد Observable يكتمل عند إرسال الرسالة (وليس عند معالجتها).
// api-gateway.service.ts — جانب المستدعي import { Inject, Injectable } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { firstValueFrom } from 'rxjs'; @Injectable() export class ApiGatewayService { constructor( @Inject('ORDERS_SERVICE') private readonly ordersClient: ClientProxy, ) {} async getOrder(id: number) { // send() — يتوقّف حتى يردّ المعالج البعيد return firstValueFrom( this.ordersClient.send<{ id: number; status: string }>( { cmd: 'get_order' }, { id }, ), ); } placeOrder(payload: { userId: number; items: string[] }) { // emit() — إطلاق ونسيان؛ لا ينتظر رد المعالج this.ordersClient.emit('order.placed', payload).subscribe(); } }
حوّل الـ Observables باستخدام firstValueFrom. تُعيد ClientProxy.send() Observable من RxJS لا Promise. لُفّها بـ firstValueFrom() (من rxjs) لاستخدامها بسهولة ضمن كود async/await. لا تُهمل الاشتراك — Observable غير مُشترك فيه لا ينفّذ أبدًا.

معالجة الحمولة والسياق

استخدم @Payload() لاستخراج بيانات الرسالة و@Ctx() للوصول إلى السياق الخاص بطبقة النقل (الترويسات، دوال الإقرار، معلومات التقسيم، وغيرها):

import { MessagePattern, Payload, Ctx, KafkaContext } from '@nestjs/microservices'; @MessagePattern('inventory.check') async checkInventory( @Payload() data: { sku: string; qty: number }, @Ctx() context: KafkaContext, ) { const topic = context.getTopic(); const partition = context.getPartition(); console.log(`Message from topic ${topic}, partition ${partition}`); return { sku: data.sku, available: true }; }
لا تخلط بين send() وemit() مع المُزخرف الخاطئ. استدعاء client.send() مقابل معالج مُسجَّل بـ @EventPattern سيتعلّق إلى الأبد منتظرًا ردًّا لن يأتي. واستدعاء client.emit() مقابل معالج @MessagePattern يعني إهمال ردّك — لا يحصل المستدعي على شيء. اجعل الزوج متّسقًا دائمًا.

الخلاصة

تُقدّم خدمات NestJS المصغّرة بدائل تواصل أساسية: @MessagePattern + ClientProxy.send() للطلب-الاستجابة (يُكمل المستدعي await ردًّا)، و@EventPattern + ClientProxy.emit() للإطلاق والنسيان (لا يُتوقَّع رد). استخدم @Payload() للوصول إلى بيانات الرسالة و@Ctx() لسياق النقل. طابق دائمًا المُزخرف على المعالج مع أسلوب الـ proxy الصحيح على المستدعي — فالأخطاء هنا تؤدّي إلى فشل صامت.