# RabbitMQ — תור הודעות וחלוקת עבודה📖 שיעור

## מה זה?

בשיעור הקודם למדנו לבחור בין תקשורת sync ל-async, עם Kafka כדוגמת ה-async היחידה. אבל יש בעיה ש-Kafka לא נועד לפתור בצורה הפשוטה ביותר: כששירות ה-Orders מפרסם "צריך לשלוח מייל אישור", רוצים שבדיוק **worker אחד** מתוך קבוצה יטפל בכל מייל — לא שכל מופע (instance) של Email Service ישלח את אותו מייל בנפרד. RabbitMQ פותר בדיוק את זה: תור הודעות (Message Queue) קלאסי שמחלק משימות בין קבוצת workers, כשכל הודעה מטופלת בדיוק פעם אחת, עם אישור (Acknowledgment) מפורש שה-worker באמת סיים — ואם הוא קורס באמצע, ההודעה חוזרת לתור אוטומטית.

## מילות מפתח שחשוב לזכור

• Queue — תור הודעות; כל הודעה ממתינה בו עד ש-worker פנוי לוקח אותה

• Producer — הצד ששולח (מפרסם) הודעה לתור

• Consumer (Worker) — הצד שמקבל הודעה מהתור ומעבד אותה

• Acknowledgment (ack) — אישור מפורש מה-Consumer ש"סיימתי לטפל בהודעה" — רק אז היא נמחקת סופית מהתור

• Work Queue — תבנית שבה כמה workers "מתחרים" על אותו תור, וכל הודעה הולכת ל-worker אחד בלבד (Competing Consumers)

```csharp
var factory = new ConnectionFactory { HostName = "localhost" };
using var connection = await factory.CreateConnectionAsync();
using var channel = await connection.CreateChannelAsync();

await channel.QueueDeclareAsync(
    queue: "email-tasks", durable: true,
    exclusive: false, autoDelete: false);

await channel.BasicPublishAsync(
    exchange: "", routingKey: "email-tasks", body: taskJson);
```

```csharp
// Consumer — acknowledges only after the work actually succeeds
consumer.ReceivedAsync += async (sender, ea) =>
{
    var task = Encoding.UTF8.GetString(ea.Body.ToArray());
    try
    {
        await SendEmailAsync(task);
        await channel.BasicAckAsync(ea.DeliveryTag, false);   // done — remove from queue
    }
    catch
    {
        await channel.BasicNackAsync(ea.DeliveryTag, false, true);   // failed — requeue
    }
};
await channel.BasicConsumeAsync(queue: "email-tasks", autoAck: false, consumer: consumer);
```

## הסבר עיקרי

### Work Queue — צרכנים מתחרים

בניגוד לרעיון של "כולם מקבלים את ההודעה", ב-RabbitMQ תור עם כמה workers מפזר את ההודעות ביניהם — כל הודעה הולכת ל-worker אחד בלבד (בברירת מחדל, round-robin). זה בדיוק הפתרון לבעיה מתחילת השיעור: אפשר להריץ 3 מופעים של Email Service, וכל מייל עדיין יישלח בדיוק פעם אחת — לא שלוש פעמים.

### Acknowledgment — למה `autoAck: false`

`autoAck: true` אומר ל-RabbitMQ "תחשיב את ההודעה כמטופלת ברגע שהיא נשלחה ל-worker" — אם ה-worker קורס באמצע העיבוד, ההודעה כבר "נעלמה" מהתור לצמיתות, גם שבפועל היא לא טופלה. `autoAck: false` + `BasicAckAsync` ידני פותר את זה: ההודעה נשארת בתור (ומסומנת כ"תפוסה") עד שה-worker מאשר בפירוש שסיים. אם ה-worker קורס לפני האישור, RabbitMQ מזהה זאת ומחזיר את ההודעה לתור אוטומטית — worker אחר (או אותו אחד אחרי הפעלה מחדש) יקבל אותה שוב.

### עמידות (Durability)

`durable: true` בהצהרת התור אומר ש-RabbitMQ עצמו יזכור אותו אחרי restart. זה לא מספיק לבד — צריך גם לסמן את ההודעות עצמן כ-`Persistent` (נשמרות לדיסק, לא רק בזיכרון). בלי שני אלה, קריסה של RabbitMQ עצמו יכולה למחוק תור שלם עם כל המשימות הממתינות בו.

## יתרונות

חלוקת עבודה טבעית בין כמה workers, בלי לכתוב לוגיקת חלוקה בעצמכם; מנגנון retry מובנה (nack + requeue) בלי קוד נוסף; עמידה בפני קריסת worker בודד בלי לאבד משימות.

## חסרונות

לא בנוי לשידור אירועים להמונים או ל"replay" היסטוריה כמו Kafka — הודעה בדרך כלל נצרכת פעם אחת ונעלמת; רכיב תשתית נוסף לפריסה ולניהול, בדיוק כמו Kafka.

## נקודות חשובות

• RabbitMQ = תור עבודות (Work Queue) — כל הודעה ל-worker אחד בלבד, בניגוד ל-Kafka שמשדר לכל הצרכנים

• `autoAck: false` + `BasicAckAsync` ידני מבטיחים שהודעה לא הולכת לאיבוד אם worker קורס

• `BasicNackAsync(..., requeue: true)` מחזיר הודעה שנכשלה לתור לניסיון חוזר

• `durable: true` על התור + `Persistent = true` על ההודעה = שני התנאים לעמידות אחרי restart

## טעויות נפוצות

• להשתמש ב-`autoAck: true` ואז worker קורס — ההודעה "נחשבת" כמטופלת אף שלא, ואבודה לצמיתות

• לבלבל בין RabbitMQ (תור עבודות, כל הודעה ל-worker אחד) לבין Kafka (יומן אירועים, כל צרכן יכול לקרוא הכל)

• לשכוח `durable: true`/`Persistent` ואז restart של RabbitMQ מוחק תור שלם עם כל ההודעות הממתינות

## סיכום

RabbitMQ הוא message broker מבוסס תורים (queues) שמחלק משימות בין קבוצת workers — כל הודעה מטופלת בדיוק פעם אחת (Work Queue), עם אישור (Acknowledgment) מפורש שרק אחריו ההודעה נמחקת סופית, ואם worker קורס — היא חוזרת אוטומטית לתור. בניגוד ל-Kafka (יומן אירועים לשידור), RabbitMQ מתאים במיוחד לחלוקת עבודה — תרחישים כמו עיבוד מיילים, תשלומים או כל משימת רקע שצריכה טיפול פעם אחת בדיוק, לא שידור לכולם.

## דוקומנטציה רשמית

[RabbitMQ — Tutorials](https://www.rabbitmq.com/tutorials)

[RabbitMQ .NET Client Docs](https://www.rabbitmq.com/client-libraries/dotnet-api-guide)

---

## תרגילים

### תרגיל 1 — Producer/Consumer בסיסי

**המשימה:** הריצו RabbitMQ מקומית (Docker: `docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:management`), כתבו Producer שמפרסם 5 משימות לתור `greeting-tasks`, וConsumer שצורך אותן עם `autoAck: false` ומדפיס כל אחת.

**בדיקה:** כל 5 המשימות מודפסות, ותור `greeting-tasks` ריק בממשק הניהול (`localhost:15672`) אחרי שה-Consumer סיים.

### תרגיל 2 — סימולציית כישלון

**המשימה:** שנו את ה-Consumer מתרגיל 1 כך שיזרוק Exception על ההודעה השלישית (לדוגמה בלבד), עם `try/catch` ש-`BasicNackAsync(..., requeue: true)` על כישלון.

**בדיקה:** ההודעה השלישית חוזרת לתור ומעובדת שוב (אפשר לראות אותה מגיעה פעמיים ב-log).

---

## פרויקט מסכם

**המשימה:** הוסיפו תור עבודות אמיתי ל-`TasksController` (מהפרויקטים המסכמים הקודמים) — כשמשימה נוצרת, פרסמו הודעה לתור `task-notifications`, וכתבו worker נפרד (console app) שצורך אותה ומדמה שליחת התראה.

**דרישות:**
1. `POST /tasks` יוצר את המשימה ב-DB (כרגיל) **וגם** מפרסם הודעה ל-RabbitMQ עם פרטי המשימה
2. ה-worker הנפרד צורך מהתור, מדפיס "Notification sent for task X", ומאשר (ack) רק אחרי ההדפסה
3. אם ה-worker "נופל" (Exception מדומה) — ההודעה חוזרת לתור, לא נעלמת

**בדיקה:** יצירת משימה חדשה ב-API גורמת להודעה חדשה בתור; ה-worker מעבד אותה תוך שניות; כיבוי מוקדם של ה-worker (לפני ack) גורם להודעה לחזור ולהיות מטופלת מחדש כשהוא עולה שוב.

## מה בפרק הבא

בפרק הבא נעבור ליחידה חדשה — **פרויקט צוותי: מערכת מודיעין** — הפרויקט הצוותי הראשון של הקורס, שמיישם בפועל את עקרונות המיקרוסרביסים שנלמדו (Bounded Context, תקשורת sync/async) בעבודת צוות אמיתית עם ריפו וקוד-ריוויו.
