TGViewer
C# Geeks (.NET) C# Geeks (.NET) @csharpgeeks · 550 subscribers
Post #462 188
🧱 1️⃣ The Idempotency Key

if (await dbContext.MessageConsumers.AnyAsync(c =>
c.MessageId == context.MessageId &&
c.ConsumerName == nameof(NoteCreatedConsumer)))
{
return;
}

در اینجا از موارد زیر استفاده می‌کنیم:

MessageId
که از transport (یعنی context.MessageId) گرفته می‌شود

ConsumerName
تا در صورتی که چند consumer متفاوت یک پیام را پردازش کنند، هرکدام به‌صورت ایمن عمل کنند ✅

اگر یک پیام تکراری دریافت شود، پردازش کوتاه می‌شود و هیچ کاری انجام نمی‌گیرد. 🚫

نکته‌ی بسیار مهم این است که باید روی ستون‌های (MessageId, ConsumerName) در جدول MessageConsumers یک unique constraint تعریف شود تا از race condition جلوگیری شود ⚙️
به این ترتیب حتی اگر چند پردازش هم‌زمان از یک پیام وجود داشته باشد، فقط یکی از آن‌ها موفق به درج رکورد خواهد شد. 💪

⚡️ 2️⃣ Atomic Side Effects + Idempotency Record

در این الگو، هم پردازش (processing) و هم ذخیره‌ی رکورد MessageConsumer در یک تراکنش (transaction) انجام می‌شود:
using var transaction = await dbContext.Database.BeginTransactionAsync();

// write tags
dbContext.Tags.AddRange(tagEntities);

// write message-consumer record
dbContext.MessageConsumers.Add(new MessageConsumer { ... });

await dbContext.SaveChangesAsync();
await transaction.CommitAsync();


چرا این مهم است؟ 🤔

اگر پردازش شکست بخورد ❌، هیچ ورودی‌ای در MessageConsumers ثبت نمی‌شود، بنابراین پیام می‌تواند مجدداً retry شود.

اگر پردازش موفق باشد ✅، هم داده‌ها (مثل tags) و هم رکورد مربوط به پیام باهم commit می‌شوند.

در نتیجه، هیچ‌وقت در وضعیتی قرار نمی‌گیرید که کار انجام شده باشد اما پیام به‌عنوان پردازش‌شده علامت‌گذاری نشده باشد، یا برعکس.
این اساس idempotency است:

انجام دقیق یک‌بار عملیات برای هر Message ID — حتی در شرایط retry. 🔁

📬 3️⃣ Handling At-Least-Once Delivery

در بیشتر سناریوهای واقعی، تحویل پیام‌ها از نوع at-least-once است:

1️⃣ Consumer پیام را پردازش می‌کند
2️⃣ ACK شکست می‌خورد یا timeout می‌شود
3️⃣ Broker پیام را مجدداً تحویل می‌دهد
4️⃣ کد شما دوباره اجرا می‌شود

اما در این الگو، اجرای دوم با بررسی جدول MessageConsumers مواجه شده و خیلی سریع return می‌کند. ✅

نتیجه:
هیچ side effect تکراری‌ای اتفاق نمی‌افتد. 🙌
البته فقط یک استثنا وجود دارد...

🔄 Deterministic vs Non-Deterministic Handlers

وقتی handler شما با سیستم‌هایی خارج از دیتابیس تماس می‌گیرد چه می‌شود؟
مثل:

• یک Email API ✉️
• Payment Gateway 💳
• یا یک Background Job Queue 🧵
این‌ها همگی side effectهای رایج هستند که باید آن‌ها نیز idempotent باشند.

چون این تماس‌ها خارج از محدوده‌ی تراکنش دیتابیس انجام می‌شوند، ممکن است دیتابیس commit شود اما به‌دلیل اختلال شبکه پاسخ از سرویس بیرونی برنگردد.
در retry بعدی، ممکن است همان ایمیل دوباره ارسال شود یا همان کارت اعتباری دوباره شارژ شود ⚠️

به این ترتیب، وارد قلمروی Non-Deterministic Handlerها می‌شویم عملیاتی که تکرار آن‌ها ایمن نیست.

دو استراتژی اصلی برای مدیریت این وضعیت وجود دارد:

🧩 1. استفاده از Idempotency Key در فراخوانی خارجی

اگر سرویس خارجی از Idempotency Key پشتیبانی کند، یک شناسه‌ی پایدار مثلاً همان MessageId پیام را در هر درخواست ارسال کنید.

بسیاری از APIها (مثل پردازشگرهای پرداخت یا پلتفرم‌های ارسال ایمیل) اجازه می‌دهند که یک Idempotency-Key Header مشخص کنید.
در این صورت سرویس تضمین می‌کند که درخواست‌های تکراری با کلید یکسان فقط یک‌بار اجرا شوند. ✅

به‌عنوان مثال:
await emailService.SendAsync(new SendEmailRequest
{
To = user.Email,
Subject = "Welcome!",
Body = "Thanks for signing up.",
IdempotencyKey = context.MessageId
});

حتی اگر درخواست مجدداً ارسال شود، provider کلید را تشخیص می‌دهد و درخواست تکراری را نادیده می‌گیرد.
این ساده‌ترین و مطمئن‌ترین روش است، اگر وابستگی خارجی شما از آن پشتیبانی کند. 🚀

💾 2. ذخیره‌ی Intent به‌صورت محلی

اگر سرویس خارجی از idempotency key پشتیبانی نکند، می‌توانید آن را شبیه‌سازی کنید.
کافی است پیش از تماس با سرویس بیرونی، رکوردی از اقدام مورد نظر را در دیتابیس ذخیره کنید.

مثلاً جدولی به نام PendingEmails بسازید که نشان دهد کدام پیام باید ارسال شود بر اساس MessageId یا UserId.

سپس یک background process این رکوردها را خوانده و عملیات را تنها یک‌بار انجام می‌دهد.
این رویکرد deterministic است ولی پیچیدگی بیشتری دارد (جداول بیشتر و workerهای پس‌زمینه).
More from @csharpgeeks
  1. Sep 22, 2026یه مدتی قراره از دنیای NET. فاصله بگیرم، چون وقتشه برم سربازی. راستش نمیدونم این مدت رو چج…
  2. Sep 20, 2026🔥 حالا مشکل اصلی: Alert Storm فرض کن Database از دسترس خارج شده. ۱۰۰ Pod داری. هر Pod می‌…
  3. Sep 20, 2026🚨 طراحی سیستم Monitoring و Alerting در یک سیستم بزرگ فرض کن ساعت ۳ صبح است. سیستم شما با…
  4. Sep 19, 2026#Engineering_Leadership تصمیم نگرفتن هم یک تصمیم است یه چیز عجیب توی تیم‌های مهندسی: گاهی…
  5. Sep 19, 2026☑ چک‌لیست آماده‌سازی تیم، فرایندها و زیرساخت برای توسعه با AI توجه: هیچ چک‌لیستی جهان‌شمول…
  6. Sep 19, 2026📌پایان یک انتظار طولانی: اعتبارسنجی ناهمگام (Async Validation) در NET 11.
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →