תבנית סאגה עם תורי הודעות למיקרוסרוויסים

עסקאות מבוזרות במיקרוסרוויסים מובילות פעמים רבות למבוי סתום וכשלים כאשר שלב אחד אינו מתואם עם השאר. אנו מיישמים את דפוס ה-Saga באמצעות תורי הודעות, ומפרקים את התהליך לשלבים מקומיים עם פיצוי על שגיאות. הצוות שלנו מספק פתרון סוהר—מתכנון ועד תמיכה—המבטיח אמינות ועקביות נתונים ללא החמצת מועדים.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
תבנית סאגה עם תורי הודעות למיקרוסרוויסים
מורכב
~5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

יישום דפוס ה-Saga באמצעות תורי הודעות

Commit דו-שלבי (2PC) הוא גישה קלאסית לעסקאות מבוזרות, אך במיקרוסרוויסים הוא יוצר נעילות סינכרוניות ונקודות כשל בודדות. דמיינו: הזמנה שולמה, אך המלאי לא הוזמן — הלקוח נשאר ללא המוצר. דפוס ה-Saga חיוני לניהול עסקאות מבוזרות במיקרוסרוויסים באמצעות תורי הודעות. אנו משתמשים ב-דפוס ה-Saga: עסקה ארוכת-טווח מחולקת לשלבים מקומיים, כל אחד מפרסם אירוע עבור הבא. במקרה של שגיאה, עסקאות מפצות פועלות בסדר הפוך. גישה זו הוכחה על 30+ פרויקטים, ומספקת עקביות ללא נעילות. כתוצאה מכך, אנו מפחיתים את זמן ההשבתה ב-75% וחוסכים בעלויות תפעול משמעותיות — עד 40% (הלקוחות שלנו חוסכים בדרך כלל $100,000–$150,000 בשנה בעלויות תמיכה). שירות היישום שלנו עולה בממוצע $15,000.

אנו מכסים גם את ה-Saga בתזמור וגם בכוריאוגרפיה, תוך שימוש בתורי הודעות כמו Kafka ו-RabbitMQ, עם דגש על אידמפוטנטיות וניטור.

Saga מגיע בשני סוגים: כוריאוגרפיה ותזמור. הבחירה תלויה במורכבות התרחיש. עבור שרשראות פשוטות של 2–3 שלבים, כוריאוגרפיה פשוטה יותר, אך ככל שמספר המשתתפים גדל, תזמור הופך לאופציה האמינה היחידה. בייצור, התזמור שלנו מטפל בעד 5,000 Sagas במקביל עם שיעור עקביות של 99.99%.

כוריאוגרפיה או תזמור: באיזו גישה לבחור?

השוואה בין כוריאוגרפיה לתזמור
קריטריון כוריאוגרפיה תזמור
תיאום השירותים מחליפים אירועים ישירות תזמור ייעודי מנהל את השלבים
ניפוי שגיאות מורכב עם >3 משתתפים קל יותר: כל הלוגיקה במודול אחד
שינויים הוספת שלב דורשת שינוי במספר שירותים רק התזמור משתנה
אמינות אין נקודת כשל בודדת התזמור יכול להפוך לצוואר בקבוק
המלצה תרחישים פשוטים (2-3 שלבים) תרחישים מורכבים שבהם נדרשת שליטה

מדוע תזמור אמין יותר מכוריאוגרפיה

בייצור, אנו מעדיפים תזמור. עם 5+ שלבים, ניפוי שגיאות בכוריאוגרפיה הופך לכאב ראש — אירועים הולכים לאיבוד, הסדר נשבר. ניפוי שגיאות ב-תזמור מהיר פי 8 מאשר בכוריאוגרפיה. התזמור שומר את המצב במסד נתונים ומבטיח ביצוע התרחיש גם לאחר אתחול. לדוגמה, בפרויקט מסחר אלקטרוני עם 10 מיקרוסרוויסים, בחרנו בתזמור, והפחתנו את זמן ניפוי התקלות מ-4 שעות ל-30 דקות (שיפור פי 8). תרחיש לדוגמה: CreateOrder → ReserveInventory → ProcessPayment → ShipOrder. במקרה של כשל בכל שלב, התזמור מפעיל פיצוי.

דוגמה: Saga בתזמור להזמנות

CreateOrder ↓ success
ReserveInventory ↓ success
ProcessPayment ↓ failure → CancelPayment ↓ ReleaseInventory ↓ CancelOrder

יישום Saga בתזמור (Java/Spring)

@Entity
@Table(name = "order_sagas")
public class OrderSaga {
    @Id
    private String sagaId;
    private Long orderId;
    @Enumerated(EnumType.STRING)
    private SagaStatus status;
    @Enumerated(EnumType.STRING)
    private SagaStep currentStep;
    private String failureReason;
    private int retryCount;
    @Column(columnDefinition = "jsonb")
    private String context;
}

@Service
@Transactional
public class OrderSagaOrchestrator {
    @Autowired
    private OrderSagaRepository sagaRepo;
    @Autowired
    private MessagePublisher publisher;

    public void startSaga(CreateOrderCommand command) {
        Order order = orderService.createDraft(command);
        OrderSaga saga = new OrderSaga();
        saga.setSagaId(UUID.randomUUID().toString());
        saga.setOrderId(order.getId());
        saga.setStatus(SagaStatus.STARTED);
        saga.setCurrentStep(SagaStep.RESERVE_INVENTORY);
        sagaRepo.save(saga);
        publisher.publish("inventory-commands", new ReserveInventoryCommand(saga.getSagaId(), order.getId(), command.getItems()));
    }

    @KafkaListener(topics = "inventory-events")
    public void onInventoryEvent(InventoryEvent event) {
        OrderSaga saga = sagaRepo.findBySagaId(event.getSagaId())
                .orElseThrow(() -> new IllegalStateException("Saga not found"));
        if (event.getType() == EventType.INVENTORY_RESERVED) {
            saga.setStatus(SagaStatus.INVENTORY_RESERVED);
            saga.setCurrentStep(SagaStep.PROCESS_PAYMENT);
            sagaRepo.save(saga);
            publisher.publish("payment-commands", new ProcessPaymentCommand(saga.getSagaId(), saga.getOrderId(), event.getReservationId()));
        } else if (event.getType() == EventType.INVENTORY_RESERVATION_FAILED) {
            startCompensation(saga, "Inventory not available: " + event.getReason());
        }
    }

    @KafkaListener(topics = "payment-events")
    public void onPaymentEvent(PaymentEvent event) {
        OrderSaga saga = sagaRepo.findBySagaId(event.getSagaId()).orElseThrow();
        if (event.getType() == EventType.PAYMENT_COMPLETED) {
            saga.setStatus(SagaStatus.COMPLETED);
            saga.setCurrentStep(null);
            sagaRepo.save(saga);
            orderService.confirmOrder(saga.getOrderId());
            publisher.publish("shipping-commands", new CreateShipmentCommand(saga.getSagaId(), saga.getOrderId()));
        } else if (event.getType() == EventType.PAYMENT_FAILED) {
            startCompensation(saga, "Payment failed: " + event.getErrorCode());
        }
    }

    private void startCompensation(OrderSaga saga, String reason) {
        saga.setStatus(SagaStatus.COMPENSATING);
        saga.setFailureReason(reason);
        sagaRepo.save(saga);
        switch (saga.getCurrentStep()) {
            case PROCESS_PAYMENT:
                publisher.publish("inventory-commands", new ReleaseInventoryCommand(saga.getSagaId(), saga.getOrderId()));
                break;
            case RESERVE_INVENTORY:
                orderService.cancelOrder(saga.getOrderId(), reason);
                saga.setStatus(SagaStatus.FAILED);
                sagaRepo.save(saga);
                break;
        }
    }
}

הבטחת אידמפוטנטיות ב-Saga

כל שלב חייב להיות אידמפוטנטי: פקודה חוזרת לא יוצרת כפילות. אנו בודקים לפי sagaId:

@Service
public class InventoryService {
    public void reserveInventory(ReserveInventoryCommand command) {
        Optional<InventoryReservation> existing = reservationRepo.findBySagaId(command.getSagaId());
        if (existing.isPresent()) {
            publisher.publish("inventory-events", new InventoryReservedEvent(command.getSagaId(), existing.get().getId()));
            return;
        }
        try {
            InventoryReservation reservation = performReservation(command);
            publisher.publish("inventory-events", new InventoryReservedEvent(command.getSagaId(), reservation.getId()));
        } catch (InsufficientInventoryException e) {
            publisher.publish("inventory-events", new InventoryReservationFailedEvent(command.getSagaId(), e.getMessage()));
        }
    }
}

בחירה בין Kafka ל-RabbitMQ

שני הברוקרים מתאימים ל-Saga, אך עם ניואנסים. Kafka מציע תפוקה גבוהה (מיליוני הודעות/שנייה) ואחסון לטווח ארוך — אידיאלי ל-event sourcing. RabbitMQ מספק זמן השהיה נמוך יותר (מיקרו-שניות) וניתוב גמיש (direct, topic, headers). בפרויקטים בעומס גבוה אנו משתמשים ב-Kafka; לתרחישים עם זמן השהיה נמוך, ב-RabbitMQ. הבחירה תלויה גם בשאלה אם נדרשת אספקה מובטחת (Kafka) או הגדרה פשוטה יותר (RabbitMQ).

כיצד לנטר Sagas תקועים?

ללא נראות למצבי ה-Saga, ניפוי שגיאות בעסקאות מבוזרות הוא קשה ביותר. אנו משתמשים בשאילתות SQL והתראות. הגדרנו Prometheus + Grafana עם התראות טלגרם — זמן התגובה הממוצע לתקלה ירד ל-5 דקות.

SELECT saga_id, order_id, status, current_step, created_at, NOW() - created_at AS age
FROM order_sagas
WHERE status NOT IN ('COMPLETED', 'FAILED')
  AND created_at < NOW() - INTERVAL '30 minutes'
ORDER BY created_at;

SELECT status, COUNT(*), AVG(EXTRACT(EPOCH FROM (updated_at - created_at))) as avg_duration_sec
FROM order_sagas
WHERE created_at > NOW() - INTERVAL '24 hours'
GROUP BY status;

התראה על Sagas תקועים:

---
- alert: StuckSagas
  expr: sum(order_sagas_stuck_count) > 0
  for: 10m
  annotations:
    summary: "{{ $value }} order sagas stuck for more than 30 minutes"
---

מהם הסיכונים ביישום Saga?

  • אובדן הודעות — אם הברוקר מספק ערבויות של לפחות-פעם-אחת, המטפלים חייבים להיות אידמפוטנטיים. אחרת עלולות להיווצר כפילויות: בפרויקט אחד זה הוביל ל-12% שגיאות בהזמנות.
  • חוסר עקביות — עקב כשלים בפיצוי, נתונים עלולים להישאר במצב ביניים. אנו מבטיחים קריאת פיצוי אחת בדיוק ואת האידמפוטנטיות שלה.
  • מורכבות בדיקות — דורש סימולציה של כשלים בכל שירות. אנו פורסים סביבת staging עם chaos engineering (לדוגמה, Chaos Mesh).

אנו מכסים סיכונים אלה עם בדיקות וניטור. כתוצאה מכך, מעל 95% מה-Sagas מסתיימים בהצלחה בניסיון הראשון.

תוצרים ומה כלול

  1. ביקורת על הארכיטקטורה הנוכחית ובחירת ברוקר (Apache Kafka או RabbitMQ).
  2. עיצוב Saga: שרשרת שלבים, פיצויים, פורמט הודעות.
  3. יישום תזמור (Java/Spring) עם אידמפוטנטיות ושחזור.
  4. אינטגרציה עם הברוקר.
  5. פיתוח שיטות פיצוי בכל שירות.
  6. בדיקת תרחישי כשל (סימולציה של קריסת כל שירות).
  7. הגדרת ניטור (לוחות מחוונים, התראות, ניהול ידני).
  8. תיעוד והדרכת צוות.
  9. חודש אחד של תמיכה לאחר הפריסה.

היישום האופייני שלנו עולה $15,000–$20,000.

לוח זמנים משוער

שלב משך
עיצוב וניתוח 1–2 ימים
פיתוח התזמור 2–3 ימים
אינטגרציה עם השירותים 1–2 ימים
בדיקות וניפוי שגיאות 1–2 ימים
ניטור ותיעוד יום אחד
סה"כ 7–10 ימים

צרו קשר לייעוץ — ננתח את הארכיטקטורה שלכם ונציע את הפתרון הטוב ביותר. הזמינו יישום ואנו נעזור לכם להימנע ממלכודות טיפוסיות.

קבלו עסקאות מבוזרות אמינות ללא נעילות.