Inbox Pattern: مدیریت دریافت مطمئن و جلوگیری از پردازش تکراری رویدادها

در معماری‌های مدرن و به‌ویژه سیستم‌های رویدادمحور (Event-Driven Architecture)، کنترل جریان پیام‌ها و اطمینان از اجرای دقیق عملیات‌ها، یکی از چالش‌های اصلی توسعه‌دهندگان است. پس از آشنایی با Outbox Pattern که بر ارسال مطمئن رویدادها تمرکز داشت، این‌بار به سراغ الگوی مکمل آن یعنی Inbox Pattern می‌رویم؛ الگویی که وظیفه دارد از دریافت مطمئن و جلوگیری از پردازش تکراری رویدادها اطمینان حاصل کند.

چرا به Inbox Pattern نیاز داریم؟

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

هدف Inbox Pattern این است که از idempotency (تضمین اجرای یکتا) اطمینان حاصل کند؛ یعنی هر پیام فقط یک بار اثر واقعی خود را بر سیستم بگذارد، حتی اگر چندین بار دریافت شود.

ساختار و مکانیزم Inbox Pattern

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

📦 OrderService
 ┣━━ Inbox (table)
 ┃   ┣━━ MessageId (GUID)
 ┃   ┣━━ ReceivedAt (datetime)
 ┃   ┣━━ Processed (bool)
 ┃   ┣━━ Payload (json)
 ┣━━ Processor
     ┗━━ Reads unprocessed messages → Execute → Mark as processed

با این ساختار، اگر پیام تکراری با همان شناسه (MessageId) مجدداً ارسال شود، سیستم به‌راحتی تشخیص می‌دهد که این پیام قبلاً ثبت و پردازش شده است و از اجرای دوباره آن جلوگیری می‌کند.

مراحل پیاده‌سازی الگو

  1. ذخیره پیام ورودی در Inbox: بلافاصله پس از دریافت پیام، آن را در جدول Inbox ذخیره کنید. در این مرحله، وضعیت پیام «دریافت‌شده ولی پردازش‌نشده» است.
  2. بررسی وجود پیام: پیش از پردازش، بررسی کنید که آیا پیام با همان شناسه قبلاً پردازش شده است یا خیر.
  3. پردازش ایمن: در صورت جدید بودن پیام، عملیات منطقی مربوطه (مثل ثبت سفارش یا بروزرسانی وضعیت) انجام شود.
  4. علامت‌گذاری به عنوان پردازش‌شده: پس از موفقیت در پردازش، فیلد Processed پیام به true تغییر داده شود.
  5. پاک‌سازی دوره‌ای: برای جلوگیری از رشد بی‌رویه Inbox، پیام‌های قدیمی یا پردازش‌شده را به‌صورت زمان‌بندی‌شده پاک‌سازی کنید.

مثال پیاده‌سازی در C#

public class InboxMessage
{
    public Guid MessageId { get; set; }
    public string Payload { get; set; }
    public bool Processed { get; set; }
    public DateTime ReceivedAt { get; set; }
}

public async Task HandleIncomingEvent(Guid messageId, string payload)
{
    var exists = await _db.InboxMessages.AnyAsync(x => x.MessageId == messageId);
    if (exists) return; // پیام تکراری

    var msg = new InboxMessage
    {
        MessageId = messageId,
        Payload = payload,
        ReceivedAt = DateTime.UtcNow
    };
    _db.InboxMessages.Add(msg);
    await _db.SaveChangesAsync();

    // پردازش اصلی پیام
    await ProcessPayload(payload);

    msg.Processed = true;
    await _db.SaveChangesAsync();
}

مزایای استفاده از Inbox Pattern

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

چالش‌ها و نکات پیاده‌سازی

  • مدیریت رشد حجم Inbox در سیستم‌های پرترافیک نیازمند مکانیزم پاک‌سازی دوره‌ای است.
  • در پردازش موازی، باید اطمینان حاصل شود که پیام‌ها هم‌زمان پردازش نمی‌شوند (با استفاده از قفل منطقی یا transaction isolation مناسب).
  • در صورت خطای بخشی از تراکنش، باید rollback انجام شود تا وضعیت Inbox با واقعیت سیستم همخوانی داشته باشد.

ترکیب Outbox و Inbox برای قابلیت اطمینان کامل

در بسیاری از سیستم‌های enterprise، از هر دو الگو به صورت مکمل استفاده می‌شود: Outbox Pattern در سرویس ارسال‌کننده، برای اطمینان از ارسال موفق پیام؛ و Inbox Pattern در سرویس دریافت‌کننده، برای جلوگیری از پردازش تکراری. این ترکیب منجر به یک ارتباط دقیق و قابل اطمینان بین سرویس‌ها می‌شود.

Sender → Outbox → Kafka / RabbitMQ → Inbox → Receiver

جمع‌بندی و مسیر بعدی

الگوی Inbox Pattern به شما کمک می‌کند تا در سیستم‌های توزیع‌شده، اطمینان حاصل کنید که هر پیام فقط یک‌بار پردازش می‌شود — حتی در شرایطی که شبکه دچار ناپایداری یا ارسال مجدد پیام‌ها شود. این الگو همراه با Outbox، یکی از ارکان اصلی پیام‌رسانی قابل اعتماد (Reliable Messaging) در معماری مایکروسرویس‌ها محسوب می‌شود.

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