تغییر در سرویسی که از کافکا مصرف می‌کند، تا زمانی که در برابر سیستم واقعی اجرا نشود، اعتبارسنجی نمی‌شود. پارتیشن‌های واقعی، گروه‌های مصرف‌کننده واقعی و رویدادهایی که تیم‌های دیگر تولید می‌کنند، همگی بخشی از این معادله‌اند. عامل‌های کدنویسی (Coding Agents) این نیاز را تشدید می‌کنند؛ عاملی که روی یک مصرف‌کننده تکرار می‌کند، باید رویدادی منتشر کند، اثر تغییر خود را در مسیر پایین‌دست ببیند، خطا را بخواند و دوباره اجرا کند. این چرخه، ده‌ها بار برای هر تغییر و به‌صورت موازی با حلقه‌های سایر عامل‌ها تکرار می‌شود. چنین فرآیندی تنها زمانی کار می‌کند که محیطی واقع‌گرایانه، به‌صورت درخواستی و برای هر تغییر در دسترس باشد.

تست کردن مصرف‌کننده‌های کافکا سخت است. باید کافکا، تولیدکننده‌ها، مصرف‌کننده‌ها و سایر وابستگی‌های سرویس را مستقر کنید تا جریان‌ها را به‌صورت end-to-end اعتبارسنجی کنید. الگوی بهتری وجود دارد.

الگوی محیط‌های موقت روی کلاستر مشترک

برای سرویس‌های همگام، الگوی معماری که Signadot ارائه می‌دهد، دقیقاً همین امکان را فراهم می‌کند: محیط‌های موقت و سبک‌وزن روی یک کلاستر مشترک. به‌جای تکثیر کل پشته برای هر تغییر، یک محیط مشترک به‌صورت مستمر از شاخه main مستقر و اجرا می‌شود و فقط سرویس تغییر یافته کنار آن قرار می‌گیرد. درخواست‌های تست یک تگ حمل می‌کنند، هر گام ترافیک تگ‌شده را به نسخه تحت تست مسیریابی می‌کند و بقیه ترافیک به نسخه‌های پایدار مشترک هدایت می‌شود.

این مدل بر مسیریابی درخواست سوار است؛ اما مسیریابی درخواست در اولین تاپیک کافکا متوقف می‌شود. هیچ‌چیز بین تولیدکننده و مصرف‌کننده‌هایش، به‌ازای هر پیام مقصدی انتخاب نمی‌کند. تگی که در همه‌جای سیستم یک تغییر را ایزوله می‌کند، در هاپ ناهمگام (async hop) معنای خود را از دست می‌دهد.

گسترش این مدل در سراسر آن هاپ به سه مکانیسم نیاز دارد: کلید مسیریابی که روی پیام سوار می‌شود، تولیدکننده‌هایی که آن را مهر می‌کنند و مصرف‌کننده‌هایی که بر اساس آن فیلتر می‌کنند. بقیه جزئیات است و همین جزئیات است که الگو را موفق یا ناموفق می‌کند.

چرا مسیریابی درخواست در تاپیک متوقف می‌شود

فراخوانی همگام از چیزی عبور می‌کند که می‌تواند به‌ازای هر درخواست مقصد را انتخاب کند: یک سایدکار، یک پروکسی یا یک کتابخانه کلاینت. اما رکورد کافکا یک‌بار نوشته می‌شود و سپس توسط هر گروه مصرف‌کننده مشترک با سرعت و آفست خودش خوانده می‌شود. هیچ نقطه تصمیم‌گیری در زمان تحویل برای قلاب زدن وجود ندارد.

توقف مسیریابی درخواست در تاپیک کافکا

مستقر کردن مصرف‌کننده تغییر یافته کنار نسخه پایدار، به هر دو روش ممکن شکست می‌خورد. اگر آن را در گروه مصرف‌کننده پایدار قرار دهید، تخصیص پارتیشن به‌صورت دلخواه تاپیک را بین دو نسخه تقسیم می‌کند. اگر گروه جداگانه‌ای به آن بدهید، هر دو نسخه هر پیام را پردازش می‌کنند و هر عارضه جانبی دوبرابر می‌شود.

آلودگی دوطرفه

آلودگی در هر دو جهت جریان دارد. نسخه تحت تست، کد بازبینی‌نشده را روی پیام‌های دیگران اجرا می‌کند و نتایج را در وضعیت پایین‌دست مشترک می‌نویسد. از سوی دیگر، مصرف‌کننده پایدار رویدادهای تست شما را پردازش می‌کند و نمی‌توانید تشخیص دهید آیا کد جدید آن‌ها را مدیریت کرده یا نه. چون جریان ناهمگام هیچ زنجیره درخواستی برای دنبال کردن به‌جا نمی‌گذارد، حتی دیدن این آلودگی هم دشوار است.

راه‌حل‌های استاندارد، چیزهای اشتباهی را معامله می‌کنند. کلاستر تکثیرشده به‌ازای هر محیط یعنی بروکرها، کانکتورها، رجیستری اسکیما و داده‌های seed که از روز اول از محیط تولید فاصله می‌گیرند. تاپیک‌های به‌ازای هر محیط، هزینه را به خودکارسازی چرخه حیات تاپیک و سیم‌کشی مجدد پیکربندی‌ها هل می‌دهند. بروکر یک‌گره‌ای یک‌بارمصرف از Testcontainers ابزار مناسبی برای منطق مصرف‌کننده در سطح واحد است اما هیچ‌چیز درباره سیستم مشترک نمی‌گوید.

راه‌حل: کلید مسیریابی روی پیام

به هر استقرار تست یک کلید مسیریابی غیرشفاف (opaque) اختصاص دهید؛ مثلاً k7 در مثال‌های این مقاله. درخواست تست آغازگر را در لبه با آن کلید تگ کنید. در بخش‌های همگام، کلید در OpenTelemetry baggage سفر می‌کند و شما فقط به بخش انتشار کانتکست (context propagation) از OpenTelemetry نیاز دارید، نه تریسینگ.

در زمان انتشار، کلید را از کانتکست درخواست در هدرهای رکورد کپی کنید. هدرها، نه payload؛ بدنه پیام دست‌نخورده باقی می‌ماند و مصرف‌کننده‌ها می‌توانند بدون deserialize کردن فیلتر کنند. هر بروکر اصلی یک اسلات معادل دارد: ویژگی‌های پیام در SQS و Pub/Sub، هدرها در AMQP.

کلید مسیریابی در هدر پیام کافکا

کپی در کد مشترک، نه در هر سرویس

عمل کپی کردن کلید را در کد مشترک قرار دهید، نه در هر سرویس به‌طور جداگانه. ابزارسازی تولیدکننده OpenTelemetry می‌تواند این کار را انجام دهد یا یک لایه داخلی نازک می‌تواند آن را پوشش دهد. مزیت این رویکرد این است که تیم‌ها نیازی به تغییر کد سرویس خود برای پشتیبانی از کلید مسیریابی ندارند؛ زیرا این مکانیزم در لایه زیرساخت قرار می‌گیرد و شفافیت کامل را برای تولیدکننده‌ها فراهم می‌کند.

مکانیزم فیلتر در سمت مصرف‌کننده

وقتی پیام با کلید مسیریابی به تاپیک می‌رسد، مصرف‌کننده باید تصمیم بگیرد آیا آن را پردازش کند یا نه. دو رویکرد اصلی برای این فیلتر وجود دارد:

  • فیلتر در سطح کلاینت: مصرف‌کننده، هدر رکورد را قبل از پردازش بررسی می‌کند. اگر کلید مسیریابی با کلید استقرار تست مطابقت داشته باشد، پیام پردازش می‌شود؛ در غیر این صورت، رد می‌شود. این فیلتر باید قبل از هرگونه عارضه جانبی اعمال شود تا آلودگی پایین‌دست رخ ندهد.
  • فیلتر با گروه مصرف‌کننده جداگانه: نسخه تحت تست در گروه مصرف‌کننده مستقل خود قرار می‌گیرد و فقط پیام‌های دارای کلید مسیریابی مطابق را پردازش می‌کند. این رویکرد با مدل اشتراک استاندارد کافکا سازگارتر است.
فیلتر مصرف‌کننده کافکا بر اساس کلید مسیریابی

حفظ context propagation در جریان ناهمگام

یکی از چالش‌های کلیدی این الگو، حفظ context propagation در طول زنجیره ناهمگام است. وقتی سرویس A پیامی به کافکا می‌فرستد و سرویس B آن را مصرف می‌کند و سپس پیام جدیدی تولید می‌کند، کلید مسیریابی باید در هر هاپ منتقل شود. این یعنی:

  • تولیدکننده، کلید را از baggage در هدر پیام کپی می‌کند
  • مصرف‌کننده، کلید را از هدر پیام می‌خواند و در baggage درخواست‌های همگام بعدی قرار می‌دهد
  • اگر مصرف‌کننده پیام جدیدی به تاپیک دیگری تولید کند، کلید دوباره در هدر پیام جدید کپی می‌شود

این زنجیره تضمین می‌کند که کلید مسیریابی در سراسر جریان ناهمگام حفظ می‌شود و ایزولاسیون تست در هر سطح معنا پیدا می‌کند.

مزایای کلید مسیریابی نسبت به راه‌حل‌های سنتی

مقایسه الگوی کلید مسیریابی با راه‌حل‌های سنتی

این الگو چندین مزیت کلیدی ارائه می‌دهد:

  • بدون تکثیر کلاستر: یک بروکر مشترک برای همه محیط‌های تست کافی است و هزینه زیرساخت به‌شدت کاهش می‌یابد
  • بدون تغییر در payload: هدرها از بدنه پیام جدا هستند و سازگاری با سیستم‌های موجود کاملاً حفظ می‌شود
  • شفافیت برای تولیدکننده: تولیدکننده‌ها نیازی به دانستن اینکه پیام در حال تست است یا تولید ندارند
  • قابل تعمیم: الگو برای SQS، Pub/Sub و AMQP نیز به‌سادگی قابل اجرا است

ملاحظات عملی و محدودیت‌ها

این الگو بدون چالش نیست. مصرف‌کننده‌ای که پیام‌های تگ‌نشده را فیلتر می‌کند، باید بداند چه زمانی فیلتر را فعال کند و چه زمانی خاموش. در محیط تولید، هیچ فیلتری وجود ندارد و همه پیام‌ها پردازش می‌شوند. در محیط تست، فیلتر فقط پیام‌های دارای کلید مسیریابی مطابق را پردازش می‌کند و بقیه رد می‌شوند.

همچنین، مدیریت offset‌ها در این مدل نیاز به توجه دارد. مصرف‌کننده تست باید offset‌های پیام‌های فیلترشده را commit کند تا موقعیت خود در تاپیک را از دست ندهد. اما نباید این offset‌ها با offset‌های مصرف‌کننده پایدار تداخل داشته باشند. استفاده از گروه‌های مصرف‌کننده جداگانه برای نسخه‌های تست، این مشکل را به‌طور طبیعی حل می‌کند.

کلید مسیریابی، پلی بین دنیای همگام و ناهمگام می‌سازد و امکان تست واقع‌گرایانه را بدون هزینه تکثیر زیرساخت فراهم می‌کند. با گسترش عامل‌های کدنویسی و نیاز فزاینده به محیط‌های تست درخواستی، الگوهایی مانند این که ایزولاسیون را با بهره‌وری زیرساختی ترکیب می‌کنند، به بخشی ضروری از جعبه‌ ابزار مهندسی پلتفرم تبدیل خواهند شد. تیم‌هایی که امروز روی این الگوها سرمایه‌گذاری می‌کنند، فردا می‌توانند تغییرات کافکا را با همان سرعت و اطمینان سرویس‌های همگام اعتبارسنجی کنند.