مایکروسرویس با NestJS: کی RabbitMQ، کی gRPC، کی همان ماژول مونو؟
تصمیمگیری عملی برای مرز سرویس، پیامرسانی ناهمگام با RabbitMQ و قراردادهای همگام با gRPC در اکوسیستم NestJS.
علی مرتضوی
بنیانگذار پارادایس کد
اول مرز دامنه، بعد تکنولوژی پیام
تیمها اغلب با صف و gRPC شروع میکنند چون «مدرن» به نظر میرسد. هزینهٔ واقعی مایکروسرویس در شبکه، مشاهدهپذیری، نسخهٔ قرارداد و عملیات است—نه در دکوراتورهای Nest. اگر تیم کوچک است و دامنه هنوز جابهجا میشود، مونولیت ماژولار با مرزهای شفاف معمولاً سریعتر و پایدارتر است.
سرویس را وقتی جدا کنید که چرخهٔ انتشار مستقل، مقیاس متفاوت، یا مالکیت دادهٔ جدا لازم دارید. اگر دو ماژول همیشه با هم deploy میشوند و یک دیتابیس دارند، جداسازی فقط تأخیر و پیچیدگی میخرد.
RabbitMQ برای کارهایی که میتوانند صبر کنند
صف برای رویدادها و کار پسزمینه است: ارسال ایمیل، تولید فاکتور، همگامسازی جستجو، پردازش تصویر. تولیدکننده باید سریع ack منطقی بگیرد و کار سنگین را به مصرفکننده بسپارد. الگوی transactional outbox جلوی «دیتابیس commit شد ولی پیام نرفت» را میگیرد—این باگ در پروداکشن دیر و گران دیده میشود.
مصرفکنندهها باید idempotent باشند. پیام تکراری اتفاق میافتد؛ کلید یکتای کسبوکار یا جدول processed-events جلوی دوبار شارژ یا دوبار ایمیل را میگیرد. Prefetch و DLQ را از روز اول تنظیم کنید، نه بعد از اولین حادثه.
gRPC برای مسیرهای همگام و قراردادی
gRPC وقتی میدرخشد که latency پایین، قرارداد تایپشده، و فراخوانی داخلی سرویس-به-سرویس دارید: بررسی موجودی، قیمتگذاری، دریافت پروفایل در مسیر درخواست کاربر. Protobuf نسخه و سازگاری را جدیتر از JSON آزاد میکند—به شرطی که قوانین سازگاری را رعایت کنید.
gRPC را از اینترنت عمومی دور نگه دارید مگر با gateway و سیاست امنیتی مشخص. برای کلاینت وب، BFF یا API HTTP بماند؛ gRPC را پشت شبکه داخلی بگذارید.
ضدالگو: چتچت همزمان روی صف
صف را جای request/response همزمان با timeout کوتاه استفاده نکنید مگر مجبور باشید. این الگو پیچیدگی correlation id، صف پاسخ، و شکستهای مبهم میآورد؛ در حالی که gRPC یا HTTP داخلی همان کار را سادهتر حل میکند. RabbitMQ را برای fire-and-forget یا workflowهای مرحلهای نگه دارید.
اگر واقعاً نیاز به پاسخ دارید، الگوی saga/orchestration با state ماشین روشن بهتر از RPC جعلی روی پیام است.
قرارداد سرویس و نسخهگذاری
هر پیام و هر proto باید مالک، نسخه، و سیاست سازگاری داشته باشد. فیلد را حذف نکنید؛ deprecate کنید. مصرفکنندههای قدیمی باید بتوانند فیلد جدید را نادیده بگیرند. قرارداد را در ریپوی جدا یا پکیج نسخهدار منتشر کنید تا تیمها روی رشتهٔ آزاد توافق نکنند.
در Nest، تعریف handlerها را نازک نگه دارید: اعتبارسنجی ورودی، فراخوانی use-case، نگاشت خطا. منطق دامنه داخل transport نرود تا روزی که transport عوض شد، دامنه زنده بماند.
مشاهدهپذیری در سیستم توزیعشده
بدون correlation id مشترک بین HTTP، پیام و gRPC، دیباگ پروداکشن غیرممکن میشود. trace را از لبه تا مصرفکننده صف ادامه دهید. متریکهای صف—depth، age، retry، DLQ—را همسطح latency API بدانید.
برای هر سرویس، داشبورد طلایی بسازید: نرخ خطا، saturation، و backlog. هشدار روی «صف در حال رشد و مصرفکننده مرده» باید زودتر از هشدار CPU بیاید.
مسیر مهاجرت بدون نمایش بزرگ
از ماژولهای مرزی داخل مونولیت شروع کنید، رویداد را منتشر کنید حتی اگر هنوز مصرفکننده داخلی است، بعد مصرفکننده را جدا کنید. وقتی ترافیک و مالکیت پایدار شد، داده را جدا کنید—نه برعکس. جداسازی دیتابیس زودهنگام دردناکترین قسمت است.
NestJS برای این مسیر مناسب است چون همان الگوهای module/provider را در مونو و میکرو نگه میدارد؛ اما موفقیت از نظم دامنه میآید، نه از تعداد microserviceها.
سؤالات متداول
از همان روز اول مایکروسرویس بسازیم؟
معمولاً خیر. با مونولیت ماژولار و مرزهای واضح شروع کنید؛ وقتی نیاز انتشار/مقیاس مستقل ثابت شد جدا کنید.
RabbitMQ یا Kafka؟
برای صف کار و رویدادهای عملیاتی با مصرفکنندههای محدود، RabbitMQ ساده و کافی است. Kafka وقتی معنا دارد که stream بزرگ، replay و چند مصرفکننده مستقل محور سیستم باشد.
چطور از پیام تکراری خلاص شویم؟
کاملاً خلاص نمیشوید؛ idempotency میسازید. کلید یکتای کسبوکار و ذخیرهٔ پیامهای پردازششده ضروری است.
آیا gRPC برای فرانت مناسب است؟
برای مرورگر معمولاً خیر. از BFF/HTTP استفاده کنید و gRPC را برای ارتباط داخلی سرویسها نگه دارید.
دانش و مقالات
نیاز به اجرای همین مفاهیم در محصولتان دارید؟
پارادایس کد از مشاوره تا پیادهسازی کامل کنار شماست.