Node.js میں ساگا پیٹرن: مائیکرو سروسز میں تقسیم شدہ لین دین کو کیسے واپس کیا جائے۔

متعدد مائیکرو سروسز میں قابل اعتماد ورک فلو بنانا مشکل ہے۔ یک سنگی میں، ڈیٹا بیس کے لین دین اس بات کو یقینی بنا سکتے ہیں کہ متعدد آپریشنز ایک ساتھ کامیاب ہوں یا ناکام ہوں۔ تاہم، جب ڈیٹا متعدد سروسز اور ڈیٹا بیس میں پھیل جاتا ہے، تو یہ گارنٹی غائب ہو جاتی ہے۔

یہ وہ جگہ ہے جہاں ساگا پیٹرن کھیل میں آتا ہے۔ تقسیم شدہ لین دین کو استعمال کرنے کے بجائے، Saga مقامی لین دین کی ایک سیریز کو مربوط کرتا ہے اور جب کچھ غلط ہو جاتا ہے تو معاوضہ کی کارروائیاں انجام دیتا ہے۔

اس مضمون میں، ہم NestJS، gRPC، PostgreSQL، اور Sequelize کا استعمال کرتے ہوئے ایک مربوط ساگا پیٹرن بناتے ہیں۔ آپ یہ سیکھیں گے کہ کس طرح تمام خدمات میں کام کو مربوط کرنا، انعام پر مبنی رول بیکس کو لاگو کرنا، آئیڈیمپوٹینسی کو ہینڈل کرنا، اور پروڈکشن طرز کے مائیکرو سروس آرکیٹیکچر میں ورک فلو کی پیشرفت کو ٹریک کرنا ہے۔

انڈیکس

شرطیں

یہ مضمون فرض کرتا ہے کہ آپ پہلے سے ہی کچھ پسدید ترقیاتی تصورات سے واقف ہیں۔ ساگا پیٹرن کے ساتھ کسی پیشگی تجربے کی ضرورت نہیں ہے، لیکن آپ کو درج ذیل سے واقف ہونا چاہیے:

  • JavaScript، TypeScript، Node.js

  • NestJS بنیادی باتیں (کنٹرولرز، خدمات، انحصار انجکشن)

  • پوسٹگری ایس کیو ایل کے بنیادی تصورات

  • ڈیٹا بیس ٹرانزیکشن

  • ڈوکر (مقامی ترقی کے لیے تجویز کردہ)

  • مائیکرو سروس فن تعمیر کی بنیادی باتیں

  • gRPC کی بنیادی باتیں (مفید، لیکن ضروری نہیں)

اگر آپ پہلے ہی NestJS اور PostgreSQL کا استعمال کرتے ہوئے کچھ بیک اینڈ سروسز بنا چکے ہیں، تو آپ کے پاس وہ سب کچھ ہوگا جس کی آپ کو اس گائیڈ پر عمل کرنے کی ضرورت ہے۔

1. تعارف

کوئی راستہ نہیں موت مقامی لین دین کا ایک سلسلہ جو متعدد خدمات پر محیط ہے۔ ہر قدم اپنے ڈیٹا بیس کے لین دین کا ارتکاب کرتا ہے۔ اگر اس کے بعد کے اقدامات ناکام ہو جاتے ہیں تو، کہانی چل جائے گی۔ لین دین کے انعامات معنوی طور پر پہلے سے طے شدہ کام کو منسوخ کرتا ہے۔

اس پیٹرن کو پہلی بار ہیکٹر گارسیا-مولینا اور کینتھ سیلم نے 1987 میں طویل مدتی ڈیٹا بیس کے لین دین کے لیے بیان کیا تھا۔ یہ 10 سال پہلے اس وقت دوبارہ دریافت ہوا جب کمپنیوں نے مائیکرو سروسز میں یک سنگی کو توڑنا شروع کیا اور محسوس کیا کہ ان کے بیک اینڈ ڈویلپر بیلٹ میں واحد سب سے طاقتور ٹول، ڈیٹا بیس ٹرانزیکشن، سروس کی حدود میں کام نہیں کرتا ہے۔

یہ مضمون آپ کو ایجنسی کی آن بورڈنگ کے لیے Node.js (NestJS + gRPC) سے اخذ کردہ کہانی کے بارے میں بتاتا ہے۔ یہاں، دونوں سروسز کا ایک ہی کاروباری نتیجہ پر متفق ہونا ضروری ہے۔

  • agency-service – ایجنسی کے ریکارڈ کا مالک ہے۔

  • auth-service – تنظیموں، صارفین اور کرداروں کا مالک ہے۔

اگر یا تو ناکام ہو جاتا ہے، تو سسٹم کو بند کر دینا چاہیے جیسے کچھ ہوا ہی نہیں۔ کوئی آدھے تخلیق شدہ صارف، یتیم تنظیمیں، یا 3am Slack دھاگے نہیں۔

2. ایک تصویر میں مسائل

کیڑے جو روکنے کے لئے بنائے گئے ہیں وہ ہیں:

Step 1: auth-service     ✅ creates Organization #42
Step 2: auth-service     ✅ creates User #99
Step 3: agency-service   ❌ fails (DB down, validation, network blip…)

Result without a saga:
   Organization #42 and User #99 still exist.
   There is no Agency row.
   The user can log in but has nothing to manage.
   Support gets a ticket. Engineer writes a one-off SQL cleanup.
   Repeat every week.

ساگا کا مشن یہ پتہ لگانا ہے کہ فیز 3 ناکام ہو گیا ہے اور تنظیم #42 اور صارف #99 کو واضح طور پر حذف کریں۔لہذا سسٹم دوبارہ مستقل ہے، چاہے وہ قطار کسی اور سروس کے ڈیٹا بیس میں ہو۔

3. آپ کو ایک کہانی کی ضرورت کیوں ہے۔

یک سنگی میں، آپ ہر چیز کو ایک DB ٹرانزیکشن میں لپیٹ دیتے ہیں اور ڈیٹا بیس کو ایٹمیسیٹی کو سنبھالنے دیتے ہیں۔

await sequelize.transaction(async (tx) => {
  await Organization.create({...}, { transaction: tx });
  await User.create({...}, { transaction: tx });
  await Agency.create({...}, { transaction: tx });
});

مائیکرو سروسز میں، ہر سروس کا اپنا ڈیٹا بیس ہوتا ہے۔ آپ ایک ACID لین دین میں دو خدمات کو لپیٹ نہیں سکتے۔ کلاسک متبادل میں تمام مسائل ہیں۔

اختیارات مسئلہ
دو فیز کمٹ (2PC) آپ پوری سروس میں قطاروں کو مقفل کرتے ہیں اور کوآرڈینیٹر ناکامی کا واحد نقطہ ہے اور اسکیل نہیں کرتا ہے۔ زیادہ تر جدید ڈیٹا بیس HTTP/gRPC کو صحیح طریقے سے سپورٹ نہیں کرتے ہیں۔
"میں صرف امید کرتا ہوں کہ یہ کام کرتا ہے۔” اگر نصف بہاؤ ناکام ہوجاتا ہے، تو یہ یتیم صارف/بلنگ قطاروں کو چھوڑ دیتا ہے۔ اصل ڈیٹا کی بدعنوانی – جتنا طویل نظام چلتا ہے، اتنے ہی زیادہ یتیم جمع ہوتے ہیں۔
دستی صفائی کا اسکرپٹ یہ ایک ہفتے تک کام کرتا ہے۔ کیڑے مہینوں تک چھپ جاتے ہیں۔ نئے انجینئر نہیں جانتے کہ وہ موجود ہیں۔
معاوضہ کے بغیر حتمی مستقل مزاجی یہ کچھ ڈومینز (تجزیہ) کے لیے ٹھیک ہے، لیکن بلنگ، شناخت، یا رقم سے متعلق کسی بھی چیز کے لیے، یہ بالکل غلط ہے۔
ساگا پیٹرن ہر خدمت مقامی طور پر انجام دیتی ہے۔ آرکیسٹریٹر ورک فلو کا مالک ہے اور ناکامی کی صورت میں واضح انعامات دیتا ہے۔ یہ قابل سماعت، دوبارہ شروع کرنے کے قابل، اور معقول ہے۔

یہ سلسلہ تقسیم شدہ لاکنگ کے بغیر واضح، قابل سماعت رول بیک پاتھ کے ساتھ حتمی مستقل مزاجی فراہم کرتا ہے۔

4. کوریوگرافی بمقابلہ آرکیسٹریشن

ساگا کو نافذ کرنے کے دو طریقے ہیں۔

کوریوگرافی۔

کوریوگرافی کے ذریعے، خدمات واقعات کو خارج کرتی ہیں اور دیگر خدمات سبسکرائب کرتی ہیں اور ردعمل ظاہر کرتی ہیں۔

auth-service → emits "UserCreated"
agency-service → listens, creates agency, emits "AgencyCreated"
billing-service → listens, creates subscription…

شروع میں سادہ مگر بعد میں نازک۔ ورک فلو N کوڈ بیسز میں تقسیم کیا جاتا ہے۔ کوئی بھی اس کا مالک نہیں ہے۔ ڈیبگنگ کا مطلب ہے پورے لاگ میں واقعات کا سراغ لگانا۔ اقدامات شامل کرنے کا مطلب ہے متعدد خدمات کو تبدیل کرنا۔

آرکیسٹریشن

آرکیسٹریشن میں، ایک خدمت کنڈکٹر ہے۔ باری باری دوسروں کو بلائیں۔

orchestrator:
   1. authClient.provisionAccount(...)
   2. agencyRepo.create(...)
   3. authClient.sendWelcomeEmail(...)

یہاں کچھ اور جوڑے ہیں (آرکیسٹریٹر کو کلائنٹ مل جاتا ہے)، لیکن پورا ورک فلو ایک فائل میں ہے۔ ایک نئے انجینئر کو جہاز میں لانے میں ایک گھنٹہ لگتا ہے۔ ایک قدم شامل کرنا ایک واحد PR ہے۔

جب تک کہ آپ کے پاس نہ کرنے کی زبردست وجہ نہ ہو، آرکیسٹریشن کا انتخاب کریں۔ یہ مضمون اور حوالہ عمل آرکیسٹریشن کا استعمال کرتا ہے۔

5. مثال پروجیکٹ

یہاں ہمارا مقصد سسٹم میں ایجنسی بنانا ہے۔ یہ وہ لمحہ ہے جب ایک نیا B2B کسٹمر سائن اپ کرتا ہے۔

ایک نتیجہ پر متفق ہونے کے لیے دو خدمات درکار ہیں۔

auth-service آپ کو تخلیق کرنے کی ضرورت ہوگی:

  • نہیں Organization قطار (کرایہ دار)

  • کوئی راستہ نہیں User قطار (لاگ ان کرنے کے لیے ایجنسی مینیجر)

  • کوئی راستہ نہیں UserRole صارف AGENCY_ADMIN کردار

agency-service آپ کو تخلیق کرنے کی ضرورت ہوگی:

  • نہیں Agency کاروباری تفصیلات پر مشتمل قطاریں (سائز، رجسٹریشن نمبر، ویب سائٹ، برانچ…) اوپر صارف/تنظیم سے منسلک ہیں۔

اس قطار کا ایک غیر ملکی کلیدی تعلق ہے۔ کے اندر اگرچہ یہ ایک خدمت ہے، ~ نہیں تمام خدمات – پوسٹگریس اس بات کو یقینی بناتا ہے کہ تصدیق شدہ DB میں صارفین authUserId یہ ایجنسی DB میں ہے۔ درخواست کو یہ کرنا ہوگا۔

auth-service DB                    agency-service DB
─────────────────                  ─────────────────
organizations  ◄────────┐
   │                    │
   │ (1:1)              │   foreign reference (no FK)
   ▼                    │           agencies
users  ──────► user_roles                     ─ authUserId
                                              └ authOrganizationId

اگر مرحلہ 2 ناکام ہوجاتا ہے۔ ~ بعد اگر مرحلہ 1 کامیاب ہو جاتا ہے، تو آپ کے پاس ایک صارف رہ جائے گا جو تصدیق کر سکتا ہے لیکن اس کی کوئی ایجنسی نہیں ہے۔ مرحلہ 2 میں بالکل یہی بگ ہے۔ یہ بالکل وہی ہے جسے یہ کہانی روکتی ہے۔

6. فن تعمیر

                     ┌───────────────────────────────┐
                     │        API Gateway            │
                     └──────────────┬────────────────┘
                                    │ HTTP
                                    ▼
   ┌──────────────────────────────────────────────────┐
   │              agency-service                      │
   │   ┌─────────────────────────────────────────┐    │
   │   │   AgencyOnboardingOrchestrator (SAGA)   │    │
   │   └───────────────┬─────────────────────────┘    │
   │                   │ writes state                 │
   │                   ▼                              │
   │      agency_onboarding_sagas  (Postgres)         │
   └───────────────┬─────────────────┬────────────────┘
                   │ gRPC            │ gRPC
       provisionAgencyAccount   compensateAgencyAccount
                   │                 │
                   ▼                 ▼
   ┌──────────────────────────────────────────────────┐
   │              auth-service                        │
   │   AgencyProvisioningService  (Participant)       │
   │                                                  │
   │   organizations · users · user_roles             │
   │   agency_provision_records  ← idempotency log    │
   └──────────────────────────────────────────────────┘

تین اجزاء تمام کام کرتے ہیں۔

  1. AgencyOnboardingOrchestrator کو agency-service – ورک فلو کو چلائیں۔

  2. agency_onboarding_sagas میں میز agency-service – کہانی کی ترقی کا ایک پائیدار لاگ۔

  3. AgencyProvisioningService کو auth-service – ایکسپوژر do کام (provisionAgencyAccount) اور undo کام (compensateAgencyAccount)۔ یہ خود معاون ہے۔ agency_provision_records بے حس ٹیبل۔

آرکیسٹریٹر توثیق کے ڈیٹا بیس تک براہ راست رسائی نہیں کرتا ہے۔ حدود gRPC کے ذریعے نافذ کی جاتی ہیں۔

7. ساگا فلو، قدم بہ قدم

یہ ترتیب کا خاکہ آن بورڈنگ ساگا کا مکمل لائف سائیکل دکھاتا ہے۔ ورک فلو اس وقت شروع ہوتا ہے جب کوئی کلائنٹ ایک نئی ایجنسی بنانے کی درخواست بھیجتا ہے۔ آرکیسٹریٹر سب سے پہلے ڈیٹا بیس میں ساگا ریکارڈ بناتا ہے اور اسے اس طرح دکھاتا ہے: STARTEDکاروباری کاموں کو انجام دینے سے پہلے ورک فلو کا مسلسل ریکارڈ فراہم کرتا ہے۔

اعلیٰ سطح پر، آرکیسٹریٹر ایک ساگا ریکارڈ بنا کر شروع کرتا ہے اور پھر پوچھتا ہے: auth-service تنظیموں، صارفین، اور کرداروں کی فراہمی۔ اگر کامیاب ہو تو آرکیسٹریٹر اپنے ڈیٹا بیس میں ایجنسی کا ریکارڈ بناتا ہے۔

اگر تمام اقدامات کامیاب ہیں تو، کہانی ہے COMPLETED صورت حال اگر تصدیقی وسیلہ پہلے سے ہی تخلیق ہونے کے بعد ایجنسی کی تخلیق ناکام ہو جاتی ہے، تو آرکیسٹریٹر معاوضے کا ایک مرحلہ شروع کرتا ہے جو کہ ہدایات دیتا ہے: auth-service پہلے سے فراہم کردہ کسی بھی چیز کو ہٹا دیں۔

بنیادی خیال یہ ہے کہ جب کہ ہر سروس اپنے مقامی لین دین کا ارتکاب کرتی ہے، یہ سلسلہ پورے کاروباری ورک فلو کو مربوط کرتا ہے اور اس بات کو یقینی بناتا ہے کہ خرابیاں ہونے پر سسٹم مستقل حالت میں واپس آ سکے۔

sequenceDiagram
    autonumber
    participant C as Client
    participant AS as agency-service
Orchestrator participant DB1 as saga store participant AU as auth-service participant DB2 as auth DB C->>AS: POST /agencies AS->>DB1: INSERT saga (STARTED, payload) AS->>AU: provisionAgencyAccount(sagaId, …) AU->>DB2: BEGIN TX AU->>DB2: create org + user + role + provision_record AU->>DB2: COMMIT AU-->>AS: { userId, organizationId, roleId } AS->>DB1: UPDATE saga (AUTH_PROVISIONED) AS->>AS: create Agency row alt Agency row OK AS->>DB1: UPDATE saga (AGENCY_CREATED → COMPLETED) AS->>AU: sendAgencyWelcomeEmail (non-critical) AS-->>C: 200 OK + sagaId else Agency row fails AS->>DB1: UPDATE saga (COMPENSATING) AS->>AU: compensateAgencyAccount(sagaId) AU->>DB2: BEGIN TX AU->>DB2: delete role + token + user + org + record AU->>DB2: COMMIT AS->>DB1: UPDATE saga (COMPENSATED → FAILED) AS-->>C: 5xx + error code end

ایک بار جب آپ اسے کور سے کور تک پڑھ لیں گے، تو آپ پورے آن بورڈنگ ورک فلو کو سمجھ جائیں گے۔ یہ آرکیسٹریشن کی قدر ہے۔ ترتیب کا خاکہ ہے عمارت

8. ریاستی مشین

تمام تبدیلیاں لاگ ان ہیں: agency_onboarding_sagas پہلے مندرجہ ذیل مراحل پر عمل کیا جاتا ہے: یہ وہی ہے جو اس کہانی کو قابل مشاہدہ اور بازیافت کرتا ہے۔

export enum AgencyOnboardingSagaStatus {
  STARTED            = 'STARTED',            // Row exists, no side effects yet
  AUTH_PROVISIONED   = 'AUTH_PROVISIONED',   // Auth side committed
  AGENCY_CREATED     = 'AGENCY_CREATED',     // Agency row committed
  COMPLETED          = 'COMPLETED',          // Happy-path terminal state
  COMPENSATING       = 'COMPENSATING',       // Rollback in progress
  COMPENSATED        = 'COMPENSATED',        // Rollback finished
  FAILED             = 'FAILED',             // Terminal failure (with or without compensation)
}

اتنی ریاستیں کیوں ہیں؟ کیونکہ ’’یہاں کیا ہوا؟‘‘ یہ ایک سوال ہے جو کوئی صبح 2 بجے پوچھے گا۔ ایک کہانی جو صرف بچاتی ہے۔ success | failure یہ فرانزک کے لیے بیکار ہے۔

                ┌── auth fails ──────────► FAILED  (nothing to compensate)
                │
STARTED ──► AUTH_PROVISIONED ──► AGENCY_CREATED ──► COMPLETED  (happy path)
                                       │
                       agency fails ───┘
                                       ▼
                                COMPENSATING
                                       │
                                       ▼
                                COMPENSATED ──► FAILED  (consistent again)

‘پوائنٹ آف نو ریٹرن’ AUTH_PROVISIONED. اس سے پہلے کہ آپ تیزی سے ناکام ہو سکتے ہیں۔ کوئی کالعدم نہیں ہے۔ اس کے بعد ناکامی کے تمام راستے ~ کرنا ہے۔ انعامات سے گزریں۔

9. آرکیسٹریٹر کا نفاذ

آرکیسٹریٹر ہے۔ صرف جہاں آپ ورک فلو کو جانتے ہیں۔ ہر قدم ایک نجی طریقہ ہے، اور ہر قدم واپس آنے سے پہلے نتیجہ رکھتا ہے۔

ساگا ریکارڈ بنائیں

// agency-onboarding.saga.repository.ts
async createSaga(payload: CreateAgencyOrchestrationInput) {
  return this.sagaModel.create({
    sagaId: randomUUID(),                          // correlation id for everything
    status: AgencyOnboardingSagaStatus.STARTED,
    currentStep: 'STARTED',
    payload,                                       // full input snapshot for replay
  });
}

کہ sagaId یہ ایک UUID ہے جو ایک بار بنایا گیا تھا۔ تمام ڈاون اسٹریم کالز پر پروپیگنڈہ کیا گیا۔. ایک واحد شناخت کنندہ جو آرکیسٹریٹر سائیڈ پر ساگا لاگ کو شریک کی طرف سے پروویژننگ ریکارڈ سے جوڑتا ہے۔

مرکزی لوپ

// agency-onboarding.orchestrator.ts (trimmed for the article)
async execute(input: CreateAgencyOrchestrationInput) {
  const saga = await this.sagaRepository.createSaga(input); // STARTED

  try {
    // Step 1 — auth-service work
    const authStep = await this.provisionAuth(saga, input);
    if (!authStep.ok) {
      await this.markFailed(saga, authStep.failure); // nothing to compensate
      return authStep.failure;
    }

    // Step 2 — agency-service work
    let activeSaga = authStep.saga; // status: AUTH_PROVISIONED
    try {
      activeSaga = await this.createAgencyRow(activeSaga, input, authStep.authIds);
    } catch (err) {
      // The expensive case: undo what auth-service did
      await this.compensateAuth(activeSaga, 'SAGA_FAILED');
      const failure = mapSagaFailure(err.message, 'SAGA_FAILED', 'CREATE_AGENCY');
      await this.markFailed(activeSaga, failure);
      return failure;
    }

    // Step 3 — mark done and run non-critical side effects
    activeSaga = await this.sagaRepository.updateSaga(activeSaga, {
      status: AgencyOnboardingSagaStatus.COMPLETED,
    });
    await this.sendWelcomeEmail(input, activeSaga); // best-effort

    return mapSagaSuccess(activeSaga, await this.agencyModel.findByPk(activeSaga.agencyId!));
  } catch (error) {
    // Defensive catch-all (lost DB connection, unexpected throw)
    await this.compensateAuth(saga, 'SAGA_FAILED');
    const failure = mapSagaFailure(error.message, 'SAGA_FAILED', 'SAGA');
    await this.markFailed(saga, failure);
    return failure;
  }
}

تفصیلی واحد قدم

private async provisionAuth(saga: AgencyOnboardingSaga, input: ...) {
  this.logger.log(`[${saga.sagaId}] PROVISION_AUTH`);

  const auth = await firstValueFrom(
    this.authClient.provisionAgencyAccount({
      sagaId: saga.sagaId,                  // <-- correlation
      organizationName: input.agencyName.trim(),
      email: input.email.trim().toLowerCase(),
      // …
    }),
  );

  if (!auth.status || !auth.data) {
    return { ok: false, failure: mapAuthProvisionFailure(auth) };
  }

  // Persist the IDs we will need if we have to compensate later
  const updated = await this.sagaRepository.updateSaga(saga, {
    authOrganizationId: Number(auth.data.organizationId),
    authUserId: Number(auth.data.userId),
    authUserRoleId: Number(auth.data.userRoleId),
    status: AgencyOnboardingSagaStatus.AUTH_PROVISIONED,
  });

  return { ok: true, saga: updated, authIds: auth.data };
}

وہ لائن جو زیادہ تر کام کرتی ہے۔ updateSaga کال واپس کی گئی غیر ملکی ID کو اسٹور کرتا ہے۔ auth-service لہذا یہاں تک کہ اگر آرکیسٹریٹر کا عمل کریش ہو جائے اور دوبارہ شروع ہو جائے، بازیابی کا کام اس لائن کو پڑھ سکتا ہے اور جان سکتا ہے کہ کس چیز کی تلافی کرنی ہے۔

عادات کی پیروی کے قابل

  • تمام کامیاب مراحل کے بعد جاری ہے۔کالعدم کرنے کے لیے درکار ID پر مشتمل ہے۔

  • اہم اور غیر اہم اقدامات میں فرق کریں۔ خوش آمدید ای میلز، آڈٹ لاگز، اور تجزیاتی ایونٹس میں شامل ہیں: ~ نہیں یہ کہانی کو دوبارہ تلاش کرنے کے قابل ہے۔ وہ اپنی پوری کوشش کرتے ہیں۔

  • ایک لاگ لائن فی تبدیلیسابقہ [${sagaId}]. گریپ ایک ڈیبگر ہے۔

10. شریک عمل درآمد

شریک (auth-service) اپنے تمام آپریشنز کو مقامی DB ٹرانزیکشن میں سمیٹتا ہے۔ اس حد کے اندر آپ کے پاس اب بھی ACID ہے۔ یہ سلسلہ صرف انٹر سروس کے مسائل کو حل کرتا ہے۔

// agency-provisioning.service.ts (trimmed)
async provisionAgencyAccount(req: ProvisionAgencyAccountInput) {

  // 1. Idempotency — return the previous result if this sagaId already provisioned.
  const existing = await this.provisionRecordModel.findOne({
    where: { sagaId: req.sagaId },
  });
  if (existing) {
    return serviceSuccess('Agency admin already onboarded', {
      userId: Number(existing.userId),
      organizationId: Number(existing.organizationId),
      userRoleId: Number(existing.roleId),
    });
  }

  // 2. Domain validation BEFORE the transaction (fail fast).
  if (await this.emailExists(req.email)) {
    return serviceFailure('Email already exists', { code: 'EMAIL_EXISTS' });
  }
  if (await this.organizationExists(req.organizationName)) {
    return serviceFailure('Organization already exists', { code: 'ORGANIZATION_EXISTS' });
  }

  // 3. The actual work — atomic at the auth-service boundary.
  return withSequelizeTransaction(this.sequelize, async (tx) => {
    const org = await this.organizationModel.create({ ... }, { transaction: tx });
    const user = await this.userModel.create({ ..., organizationId: org.id }, { transaction: tx });
    await this.userRoleModel.create({ userId: user.id, roleId: agencyAdminRole.id }, { transaction: tx });

    // The audit record that makes compensation possible later.
    await this.provisionRecordModel.create(
      { sagaId: req.sagaId, organizationId: org.id, userId: user.id, roleId: agencyAdminRole.id },
      { transaction: tx },
    );

    return serviceSuccess('Provisioned', {
      userId: user.id, organizationId: org.id, userRoleId: agencyAdminRole.id,
    });
  });
}

اس طریقہ کار کو "ساگا سیف" بنانے کی تین وجوہات ہیں:

  1. سب سے پہلے کمزوری کی جانچ کریں: اگر آرکیسٹریٹر دوبارہ کوشش کرتا ہے (نیٹ ورک بلپ، جی آر پی سی ٹائم آؤٹ)، تو دوسری کال ایک ہی ID کو واپس کرنے کے لیے ایک نہیں ہے۔ کوئی ڈپلیکیٹ صارف نہیں ہیں۔

  2. لین دین کی بیرونی تصدیق: سستا پڑھتا ہے پہلے، مہنگا لکھتا ہے دوسرے۔

  3. ایک لین دین تمام تحریروں کو لپیٹ دیتا ہے۔ اگر داخل کرنے میں ناکام ہو جاتا ہے، تو سب کچھ خود بخود واپس چلا جاتا ہے۔ آرکیسٹریٹر مکمل ناکامی کا جواب دیکھ سکتا ہے اور جانتا ہے کہ کچھ بھی برقرار نہیں رہا۔

کہ agency_provision_records میز شریک کا سب سے اہم حصہ ہے۔ کہ دونوں بے ضمیر کلید اور معاوضے کی انکوائری - ایک ہی کلید کے ساتھ داخل کی گئی۔ sagaId آرکیسٹریٹر اسے استعمال کرتا ہے۔

11. رول بیک (معاوضہ)

انعام صرف ایک اور جی آر پی سی کال ہے۔ آرکیسٹریٹر ہے۔ sagaId اور یاد رکھنے کے لیے ایک ID۔ شرکاء اپنی تخلیق کردہ ہر چیز کو حذف کر دیتے ہیں۔ الٹا انحصار کی ترتیب میںاس کے اپنے DB ٹرانزیکشن کے اندر۔

آرکیسٹریٹر کی طرف

private async compensateAuth(saga: AgencyOnboardingSaga, errorCode?: string) {
  if (!saga.authUserId && !saga.authOrganizationId) {
    // Nothing was provisioned — nothing to compensate.
    return;
  }

  // Mark the saga as compensating BEFORE the call, so the row is consistent
  // even if the compensating RPC times out.
  await this.sagaRepository.updateSaga(saga, {
    status: AgencyOnboardingSagaStatus.COMPENSATING,
    currentStep: 'COMPENSATING',
    errorCode,
  });

  try {
    const rollback = await firstValueFrom(this.authClient.compensateAgencyAccount({
      sagaId: saga.sagaId,
      organizationId: saga.authOrganizationId,
      userId: saga.authUserId,
    }));
    if (!rollback.status) {
      this.logger.error(`[\({saga.sagaId}] Auth compensation returned failure: \){rollback.message}`);
    }
  } catch (err) {
    this.logger.error(`[\({saga.sagaId}] Auth compensation RPC failed: \){err.message}`);
  }

  await this.sagaRepository.updateSaga(saga, {
    status: AgencyOnboardingSagaStatus.COMPENSATED,
    currentStep: 'COMPENSATED',
  });
}

شریک کی طرف

private async rollbackProvisionedAuth(req, sagaId: string, tx: Transaction) {
  // Use the saga log as the source of truth — even if the caller forgot IDs.
  const record = await this.provisionRecordModel.findOne({
    where: { sagaId }, transaction: tx,
  });
  const userId         = req.userId         ?? record?.userId;
  const organizationId = req.organizationId ?? record?.organizationId;

  if (userId) {
    const user = await this.userModel.findByPk(userId, { transaction: tx, attributes: ['email'] });
    await this.userRoleModel.destroy({ where: { userId }, transaction: tx });
    if (user?.email) {
      await this.passwordResetTokenModel.destroy({ where: { email: user.email }, transaction: tx });
    }
    await this.userModel.destroy({ where: { id: userId }, transaction: tx });
  }
  if (organizationId) {
    await this.organizationModel.destroy({ where: { id: organizationId }, transaction: tx });
  }
  if (record) {
    await record.destroy({ transaction: tx });
  }
}

اچھے معاوضے کے قوانین

  1. تخلیق کی ترتیب کو الٹ دیں۔ پہلے بچے (user_roles، tokens)، پھر والدین (صارفین، تنظیمیں)۔ وہی قوانین جو آپ پیروی کرتے ہیں۔ DROP TABLE اعلان

  2. بے حسی ہونی چاہیے۔ ایک ہی وصول کریں sagaId ہر بار - دو بار محفوظ رہیں۔ destroy اگر قطار پہلے ہی ختم ہو جائے تو یہ کام نہیں کرتا ہے۔

  3. ساگا لاگز کے ساتھ ساتھ درخواستوں کا بھی استعمال کریں۔ اگر بھیجنے والا اپنی ID بھول گیا ہے یا جزوی پے لوڈ بھیجا ہے، تو آپ اسے یہاں تلاش کر سکتے ہیں: sagaId. گہرائی میں دفاع۔

  4. اسے مقامی لین دین میں لپیٹیں۔ رول بیک خود جوہری ہونا ضروری ہے۔ کوئی چیز جو آدھی ختم نہیں کی گئی ہے وہ کچھ نہ کرنے سے بدتر ہے۔

  5. ہمیشہ آرکیسٹریٹر کی طرف لوپ بند کریں۔ نشان COMPENSATED چاہے RPC ناکام ہو جائے۔ غلطیاں بھی ظاہر کی جانی چاہئیں (نوشتہ جات، میٹرکس، انتباہات)۔ پھنس گیا COMPENSATING صف ایک آپریشنل کان ہے۔

اگر انعام خود ناکام ہو جائے تو کیا ہوگا؟

یہ کسی بھی ساگا ڈیزائن میں بدترین صورت حال ہے۔ تین معقول حکمت عملی ہیں:

آپ پہلے ایکسپونینشل بیک آف کا استعمال کرکے دوبارہ کوشش کر سکتے ہیں۔ یہ عارضی غلطیوں (نیٹ ورک، تعطل) کے لیے کام کرتا ہے۔

دوسرا، آپ کہانی کو مردہ خط کے طور پر بھیج سکتے ہیں۔ ہم اسے "انسانی توجہ کی ضرورت ہے" قطار اور الرٹ میں لاگ ان کرتے ہیں۔

تیسرا، آپ دستی رول بیک اینڈ پوائنٹ کو بے نقاب کر سکتے ہیں۔ اس حوالہ پر عمل درآمد کے ذریعے مکمل کیا جاتا ہے: RollbackAgencyOnboarding gRPC آپریٹرز کو وہی انعامات دوبارہ چلانے کی اجازت دیتا ہے۔ sagaId.

پیداواری نظام کو تینوں کو یکجا کرنا چاہیے۔ آپ کے لیے پیٹرن کا فیصلہ نہیں کیا گیا ہے۔ آپ اپنے کاروباری خطرے کی بنیاد پر فیصلہ کریں۔

12. ٹریکنگ، آئیڈیمپوٹینسی اور مشاہدہ

ایک ہی UUID کی طرف سے کلید کردہ دو میزیں۔ sagaIdہم اپنی تمام سروسز میں مکمل ٹریس ایبلٹی فراہم کرتے ہیں۔

آرکیسٹریٹر کی طرف - agency_onboarding_sagas

گرمی مقصد
sagaId (UUID، منفرد) تمام RPCs میں پروپیگنڈہ۔ یہ سروس وائیڈ جوائن کی کلید ہے۔
status ریاستی مشین کی موجودہ حالت۔
currentStep انسانی پڑھنے کے قابل ڈیش بورڈ لیبلز (PROVISION_AUTH، CREATE_AGENCY…)۔
payload (JSONB) ان پٹ سنیپ شاٹ — پلے بیک، ڈیبگ اور سپورٹ کے لیے استعمال کیا جاتا ہے۔
authOrganizationId، authUserId، authUserRoleId معاوضے کے لیے غیر ملکی شناخت درکار ہے۔
agencyId اگر ایجنسی کی قطار موجود ہے تو سیٹ کریں۔
errorCode، errorMessage اگر یہ ناکام ہو جائے تو اسے بھر دیا جائے گا۔
createdAt، updatedAt کہانی کی ٹائم لائن۔

حقیقی قطار COMPLETED حیثیت تقریباً اس طرح ہے:

{
  "sagaId": "0a4f3e2c-7b11-4f8d-9a2c-90b6f5f5b8a1",
  "status": "COMPLETED",
  "currentStep": "COMPLETED",
  "agencyId": 17,
  "authOrganizationId": 42,
  "authUserId": 99,
  "authUserRoleId": 3,
  "errorCode": null,
  "errorMessage": null,
  "payload": { "agencyName": "Acme Education", "email": "admin@acme.com", "...": "..." },
  "createdAt": "2026-05-22T10:14:32.118Z",
  "updatedAt": "2026-05-22T10:14:33.412Z"
}

شریک کی طرف - agency_provision_records

گرمی مقصد
sagaId (منفرد) غیرت مند کلید۔ ایک ہی sagaId آرکیسٹریٹر سے۔
userId، organizationId، roleId معاوضے پر حذف کرنے والی اشیاء۔
createdAt، updatedAt ٹائم اسٹیمپ کے لیے آپ کا شکریہ۔

مفت مشاہدہ

کیونکہ ہر لاگ لائن کے ساتھ سابقہ ​​لگا ہوا ہے۔ [${sagaId}]دونوں خدمات کے لیے ایک ہی گریپ آپ کو مکمل ٹائم لائن دے گا۔

[0a4f3e2c…] PROVISION_AUTH                  agency-service
[0a4f3e2c…] provisionAgencyAccount: ok      auth-service
[0a4f3e2c…] CREATE_AGENCY                   agency-service
[0a4f3e2c…] Agency step failed: ...         agency-service
[0a4f3e2c…] Auth compensation completed     auth-service

سٹرکچرڈ لاگنگ سیٹ اپ (لوکی، ایلسٹک سرچ، ڈیٹا ڈوگ) میں یہ ایک کلک فلٹر بن جاتا ہے۔ کہ sagaId تقسیم شدہ ٹریسنگ۔

13. ساگا کی جانچ کریں۔

ساگا صرف ایک ریاستی مشین ہے، لہذا ٹیسٹ میٹرکس محدود اور چھوٹا ہے۔ کم از کم، درج ذیل معاملات کا احاطہ کریں:

# سکرپٹ متوقع حتمی حالت
1 خوش سڑک COMPLETEDایجنسی کی موجودگی، صارف کی موجودگی
2 توثیق کا مرحلہ ناکام ہو گیا (جیسے ای میل موجود ہے) FAILEDدونوں طرف کوئی قطار نہیں
3 ایجنسی کی ناکامی COMPENSATEDمنظوری کی لائن ختم ہو چکی ہے اور کوئی ایجنسی نہیں ہے۔
4 انعام کا RPC ٹائم آؤٹ COMPENSATING → آپریٹر سے چلنے والی بحالی
5 بھیجنے والا اسی مواد کے ساتھ دوبارہ کوشش کرے گا۔ sagaId دوسری کال پہلی کال کا نتیجہ لوٹاتی ہے۔ کوئی ڈپلیکیٹ قطاریں نہیں ہیں۔
6 خوش آمدید ای میل ناکام ہو گئی۔ COMPLETED پھر بھی - معمولی اقدامات کی کوئی جھلک نہیں تھی۔

جانچ کے لیے دو عملی نکات:

سب سے پہلے، ہم نیٹ ورک کے بجائے آرکیسٹریٹر کی سطح پر جی آر پی سی کلائنٹ کا مذاق اڑاتے ہیں۔ آپ اس کا دعوی کرنا چاہتے ہیں۔ compensateAgencyAccount دائیں طرف بلایا sagaIdاس بات کی ضمانت نہیں ہے کہ وہ بائٹس کبھی ساکٹ تک پہنچ جائیں گے۔

دوسرا، اپنے انٹیگریشن ٹیسٹوں (ٹیسٹ کنٹینرز یا ڈوکر کمپوز) میں اصلی پوسٹگریس کو گھماؤ۔ postgres سروس)۔ ساگا اسٹیٹ مشین فرضی ڈیٹا بیس کے خلاف "ٹیسٹ" کرنا بہت آسان ہے اور حقیقی DB کے خلاف توڑنا بہت آسان ہے۔

14. کب ساگا استعمال نہ کریں۔

ساگا آزاد نہیں ہے۔ درج ذیل صورتوں میں چھوڑیں:

  • ایک خدمت تمام تحریریں انجام دیتی ہے۔ باقاعدہ DB لین دین کا استعمال کریں۔ پہیے کو دوبارہ ایجاد نہ کریں۔

  • ورک فلو یا تو صرف پڑھنے کے لیے ہیں یا تجزیاتی۔ SELECT میں کوئی رول بیک سیمنٹکس نہیں ہے۔

  • "رول بیک" ممکن نہیں ہے۔ میں نے آپ کو ایک حقیقی ای میل بھیجا ہے۔ میں نے اپنے کریڈٹ کارڈ سے فیس لی، لیکن گیٹ وے رقم کی واپسی کی حمایت نہیں کرتا ہے۔ ان صورتوں میں، معافی کا ای میل بھیجنے اور دستی رقم کی واپسی کی قطار بنانے کے لیے ڈیزائن کریں۔ ساگاس جسمانی اعمال کو کالعدم نہیں کر سکتے ہیں۔

  • درحقیقت، ابھی تک وہاں بہت ساری خدمات موجود نہیں ہیں۔ یک سنگی کی کہانی اوور انجینئرنگ میں سے ایک ہے۔ سروس باؤنڈری حقیقی ہونے تک انتظار کریں۔

Saga ریاستی میزیں، مرحلہ وار انعام کے طریقے، اور کام کی عادات کا سراغ لگاتا ہے۔ sagaId. یہ قیمت ادا کرنے کے قابل ہے جب متبادل یتیم ڈیٹا ہو، جو پہلے نہیں تھا۔

15. تجارت اور اسباق

اس ڈیزائن میں کیا اچھا کام ہوا:

  • ہم وقت ساز آرکیسٹریشن کوریوگرافی سے ڈیبگ کرنا آسان ہے۔ ایک نیا انجینئر ایک فائل پڑھتا ہے اور پورے بہاؤ کو سمجھتا ہے۔

  • شرکاء کی شناخت ناقابل سمجھوتہ ہے۔ آرکیسٹریٹر کی کوششیں محفوظ ہونی چاہئیں۔ پہلے دن سے تعمیر کریں۔ تبدیلیاں تکلیف دہ ہیں۔

  • ساگا ٹیبل قبائلی علم کی جگہ لے لیتا ہے۔ انتظامیہ جواب دے سکتی ہے۔ "اس سائن اپ کا کیا ہوا؟" ایک واحد SQL استفسار کے ساتھ۔ کسی واقعے کی صورت میں، پے لوڈ JSONB سونا ہے۔

  • sagaId ٹریکنگ کیز OpenTelemetry/Datadog/Loki کے ساتھ اچھی طرح کام کرتی ہیں، لہذا آپ کو کوئی اضافی انفراسٹرکچر ترتیب دینے کی ضرورت نہیں ہے۔

اس پیٹرن کو کاپی کرنے سے پہلے آپ کو جاننے کی ضرورت ہے:

  • معاوضہ دینے میں ناکامی بدترین صورت حال ہے۔ اگر compensateAgencyAccount اگر خود ہی کوئی خرابی ہو جائے تو ریاست متضاد ہو جائے گی۔ دوبارہ کوششیں + مردہ حروف + مینوئل رول بیک اینڈ پوائنٹس کے لئے شروع سے منصوبہ بنائیں۔

  • غیر اہم اقدامات کو واضح طور پر نشان زد کیا جانا چاہئے۔ یہاں خوش آمدید ای میل کو ایجنسی کے رول بیک کے بغیر ناکام ہونے کی اجازت ہے۔ غیر مستحکم SMTP فراہم کنندہ کو غلطی سے معاوضہ نہ دیں۔

  • ساگاس مقامی تجارت کی جگہ نہیں لیتے ہیں۔ ہر سروس کے اندر ہم اب بھی اصل DB ٹرانزیکشن استعمال کرتے ہیں۔ یہ کہانی صرف کراس سروس سیون کو ہینڈل کرتی ہے۔

  • ہم وقت ساز gRPC آسان ہے لیکن دستیابی کے ساتھ مل کر ہے۔ اگر auth-service یہ نیچے ہے اور ایجنسی کی تخلیق ناکام ہوجاتی ہے۔ جی آر پی سی کالز کو ایک پائیدار میسج بس (RabbitMQ/Kafka) سے بدلیں اور جب زیادہ لچک کی ضرورت ہو تو ہر قدم کو کمانڈ + رسپانس کے طور پر دیکھیں۔

  • آرکیسٹریٹر ایک اہم خدمت بن جاتا ہے۔ ہم اسی کے مطابق اپ ٹائم سنبھالتے ہیں۔ اگر خلل پڑتا ہے تو ساگا کی مدت اور انتباہات کی نگرانی کرتا ہے۔ COMPENSATING ایک قطار منتخب کریں اور ایک سے زیادہ نقل چلائیں۔

16. نتیجہ

ساگا پیٹرن جادو نہیں ہیں. یہ اس کا ایک سخت ورژن ہے جو ہنر مند انجینئر پہلے ہی ہاتھ سے کرتے ہیں۔ مقامی طور پر ارتکاب کرنے کا طریقہ سیکھیں، آپ نے کیا کیا ہے اس پر لاگ ان کریں، اور اسے کالعدم کریں۔

NestJS کے ساتھ Node.js کو صرف تین عناصر کی ضرورت ہے:

  1. ریاستی میز کہانی کو ٹریک کریں۔

  2. آرکیسٹریٹر ورک فلو کو چلائیں اور اس کی حیثیت لکھیں۔

  3. شریک یہ ہے do اور undo چابیاں کے ذریعہ بے حسی اور آپریشن sagaId.

ان تینوں چیزوں کو درست کرنے سے مائیکرو سروسز کو تقسیم شدہ تالے کی وجہ سے پیدا ہونے والی آپریشنل مشکلات کے بغیر، یک سنگی لین دین کی طرح "سب یا کچھ نہیں" کا احساس فراہم کرنے کی اجازت ملتی ہے۔

سادہ شروع کریں، آرکیسٹریشن کا استعمال کریں، تمام اقدامات کو غیرمعمولی بنائیں، کال کرنے سے پہلے ڈٹے رہیں، اور ہمیشہ جانیں کہ کیسے کالعدم کرنا ہے۔ یہ سارا نمونہ ہے۔

Scroll to Top