Skip to content
~/sph.sh

Saga Pattern ile Dağıtık Transaction'lar: ACID Olmadan Consistency Sağlamak

Microservices mimarisinde AWS Step Functions ve EventBridge kullanarak Saga pattern implementasyonu: idempotency, compensation logic ve production-ready pattern'ler.

Özet

Saga pattern, microservices mimarisinin en zorlu problemlerinden birini çözüyor: servisler arasında geleneksel ACID transaction'lar olmadan data consistency sağlamak. Bu yazıda, AWS Step Functions orchestration ve EventBridge choreography kullanarak saga implementasyonu, etkili compensation logic tasarımı, idempotency garantisi ve dağıtık sistemlerdeki isolation sorunlarını ele alıyorum. Orchestration ile choreography arasında ne zaman hangisini seçeceğini, concurrent saga conflict'leri önlemek için semantic locking'i nasıl uygulayacağını ve production ortamları için kritik observability pattern'lerini öğreneceksin.

Dağıtık Transaction Problemi

Microservices geliştirirken, hızlıca temel bir zorlukla karşılaşırsın: bağımsız servisler arasında multi-step transaction'ları koordine etmek. Geleneksel ACID transaction'lar servis sınırları arasında çalışmaz ve two-phase commit (2PC) sıkı coupling ve single point of failure yaratır.

Tipik bir e-commerce sipariş akışını düşün: sipariş oluşturma, stok rezervasyonu, ödeme işleme ve kargo onayı. Her adım farklı bir microservice ve kendi database'ine sahip. Stok rezerve edildikten sonra ödeme işleme başarısız olursa, stok rezervasyonunu geri almanın güvenilir bir yoluna ihtiyacın var. Doğru pattern'ler olmadan, servisler inconsistent hale gelir; siparişler oluşturulur, ödemeler başarısız olur, stok restore edilmez. Production'da bu tür partial failure'lar nadir değil—ağ timeout'ları, rate limit'ler veya geçici servis kesintileri compensation logic'i zorunlu kılar. Orchestration sıralı flow'lar için; choreography gevşek bağlı event stream'ler için—seçim complexity ve ekip yapısına bağlıdır. Step Functions orchestration karmaşık saga'lar için daha yönetilebilir; EventBridge choreography loose coupling sağlar.

Saga pattern bu probleme local transaction'lar ve compensating transaction'lar aracılığıyla yapılandırılmış bir yaklaşım sunuyor. Distributed transaction yerine, her adımın daha sonraki bir adım başarısız olursa compensating transaction ile geri alınabildiği bir dizi local transaction çalıştırıyorsun. Orchestration (merkezi koordinatör) veya choreography (event-driven) ile implement edilebilir; karmaşık flow'lar için Step Functions orchestration genellikle daha yönetilebilir olur. Önemli olan: her compensating transaction'ın kendi servisinde tam bir "undo" sağlaması ve idempotent olmasıdır – aynı compensation'ın iki kez çalışması ek yan etki yaratmamalıdır.

Saga Pattern Temelleri

Bir saga, şu şekilde işleyen bir dizi local transaction'dır:

  1. Her transaction tek bir servis içindeki data'yı günceller
  2. Her transaction bir sonraki transaction'ı tetiklemek için event veya message publish eder
  3. Bir transaction başarısız olursa, saga tamamlanan adımları geri almak için compensating transaction'ları çalıştırır
  4. Sistem anlık ACID consistency yerine eventual consistency sağlar

Saga'ları çalıştıran temel özellikler:

  • Eventual Consistency: Sistem hemen değil, zaman içinde consistent hale gelir
  • Local Transaction'lar: Her servis kendi database'ini local ACID garantileriyle yönetir
  • Compensating Transaction'lar: Her forward step için explicit rollback logic
  • Idempotency: Tüm saga step'leri retry-safe olmalı
  • Observability: Distributed flow'ları debug etmek için kritik—saga state'ini izleyemezsen, başarısız adımları ve compensation gereksinimlerini tespit etmek imkânsız hale gelir. Correlation ID'ler ve structured logging her saga step'inde zorunludur.

Saga pattern'i ne zaman kullanmalısın:

  • Birden fazla database'i olan microservices mimarisi
  • Birden fazla servisi kapsayan business process'ler
  • Performance veya coupling endişeleri nedeniyle distributed transaction'lar kullanılamıyor
  • Geçici inconsistency kabul edilebilir
  • Tipik olarak maksimum 3-5 step (bunun ötesinde complexity hızla artar)

Orchestration vs Choreography

Saga'ları koordine etmenin iki ana yaklaşımı var: orchestration ve choreography. Hangisini ne zaman kullanacağını anlamak başarılı implementasyon için kritik.

Choreography: Event-Driven Koordinasyon

Choreography'de servisler merkezi bir coordinator olmadan domain event'ler aracılığıyla koordine olur. Her servis bir event aldığında ne yapacağını bilir.

Avantajları:

  • Servisler arasında loose coupling
  • Single point of failure yok
  • Independent servisler için iyi scale eder
  • Event-driven architecture için doğal fit

Dezavantajları:

  • Control flow tek bir yerde görünmüyor
  • Tüm saga flow'unu anlamak zor
  • Debugging complexity (distributed logic)
  • Saga state'i track etmek daha zor
  • Cyclic dependency riski

En uygun durumlar:

  • Maksimum 3-4 servis
  • Independent, autonomous servisler
  • Mevcut event-driven architecture
  • Basit linear flow'lar

Orchestration: Merkezi Koordinasyon

Orchestration'da merkezi bir coordinator (tipik olarak AWS Step Functions) saga flow'unu yönetir, her servise ne yapacağını söyler.

Avantajları:

  • Net control flow visualization
  • Daha kolay debugging ve monitoring
  • Centralized error handling
  • Built-in state management
  • Complex flow'lar için daha iyi

Dezavantajları:

  • Orchestrator coordination point
  • Servisler orchestrator'a coupled
  • Orchestrator tüm servisleri bilmeli

En uygun durumlar:

  • Kompleks multi-step workflow'lar
  • Saga state'e visibility gerekli
  • Human approval step'leri var
  • 4'ten fazla servis involved
  • Strict ordering gereksinimleri

Karar Framework'ü

Yaklaşımını seçerken bu karar logic'ini kullan:

AWS Step Functions ile Orchestration Implementasyonu

AWS CDK kullanarak production-ready bir e-commerce sipariş saga implementasyonunu göstereyim.

Infrastructure Setup

typescript
import * as cdk from 'aws-cdk-lib';import * as sfn from 'aws-cdk-lib/aws-stepfunctions';import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks';import * as lambda from 'aws-cdk-lib/aws-lambda';import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';import { Construct } from 'constructs';
export class OrderSagaStack extends cdk.Stack {  constructor(scope: Construct, id: string) {    super(scope, id);
    // Saga state persistence için DynamoDB table'lar    const ordersTable = new dynamodb.Table(this, 'Orders', {      partitionKey: { name: 'orderId', type: dynamodb.AttributeType.STRING },      sortKey: { name: 'transactionId', type: dynamodb.AttributeType.STRING },      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST    });
    const inventoryTable = new dynamodb.Table(this, 'Inventory', {      partitionKey: { name: 'productId', type: dynamodb.AttributeType.STRING },      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST    });
    const paymentsTable = new dynamodb.Table(this, 'Payments', {      partitionKey: { name: 'paymentId', type: dynamodb.AttributeType.STRING },      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST    });
    // Forward transaction Lambda'lar    const createOrder = new lambda.Function(this, 'CreateOrder', {      runtime: lambda.Runtime.NODEJS_20_X,      handler: 'createOrder.handler',      code: lambda.Code.fromAsset('lambda'),      environment: {        ORDERS_TABLE: ordersTable.tableName      }    });
    const reserveInventory = new lambda.Function(this, 'ReserveInventory', {      runtime: lambda.Runtime.NODEJS_20_X,      handler: 'reserveInventory.handler',      code: lambda.Code.fromAsset('lambda'),      environment: {        INVENTORY_TABLE: inventoryTable.tableName      }    });
    const processPayment = new lambda.Function(this, 'ProcessPayment', {      runtime: lambda.Runtime.NODEJS_20_X,      handler: 'processPayment.handler',      code: lambda.Code.fromAsset('lambda'),      environment: {        PAYMENTS_TABLE: paymentsTable.tableName      }    });
    const confirmOrder = new lambda.Function(this, 'ConfirmOrder', {      runtime: lambda.Runtime.NODEJS_20_X,      handler: 'confirmOrder.handler',      code: lambda.Code.fromAsset('lambda'),      environment: {        ORDERS_TABLE: ordersTable.tableName      }    });
    // Compensating transaction Lambda'lar (rollback)    const cancelOrder = new lambda.Function(this, 'CancelOrder', {      runtime: lambda.Runtime.NODEJS_20_X,      handler: 'cancelOrder.handler',      code: lambda.Code.fromAsset('lambda'),      environment: {        ORDERS_TABLE: ordersTable.tableName      }    });
    const releaseInventory = new lambda.Function(this, 'ReleaseInventory', {      runtime: lambda.Runtime.NODEJS_20_X,      handler: 'releaseInventory.handler',      code: lambda.Code.fromAsset('lambda'),      environment: {        INVENTORY_TABLE: inventoryTable.tableName      }    });
    const refundPayment = new lambda.Function(this, 'RefundPayment', {      runtime: lambda.Runtime.NODEJS_20_X,      handler: 'refundPayment.handler',      code: lambda.Code.fromAsset('lambda'),      environment: {        PAYMENTS_TABLE: paymentsTable.tableName      }    });
    // Table permission'ları    ordersTable.grantReadWriteData(createOrder);    ordersTable.grantReadWriteData(confirmOrder);    ordersTable.grantReadWriteData(cancelOrder);    inventoryTable.grantReadWriteData(reserveInventory);    inventoryTable.grantReadWriteData(releaseInventory);    paymentsTable.grantReadWriteData(processPayment);    paymentsTable.grantReadWriteData(refundPayment);
    // Retry configuration ile Step Functions task'lar    const createOrderTask = new tasks.LambdaInvoke(this, 'CreateOrderTask', {      lambdaFunction: createOrder,      outputPath: '$.Payload',      retryOnServiceExceptions: true    }).addRetry({      errors: ['ThrottlingException', 'ServiceUnavailable'],      interval: cdk.Duration.seconds(2),      maxAttempts: 3,      backoffRate: 2.0    });
    const reserveInventoryTask = new tasks.LambdaInvoke(this, 'ReserveInventoryTask', {      lambdaFunction: reserveInventory,      outputPath: '$.Payload',      retryOnServiceExceptions: true    }).addRetry({      errors: ['ThrottlingException'],      interval: cdk.Duration.seconds(1),      maxAttempts: 3,      backoffRate: 1.5    });
    const processPaymentTask = new tasks.LambdaInvoke(this, 'ProcessPaymentTask', {      lambdaFunction: processPayment,      outputPath: '$.Payload',      retryOnServiceExceptions: true,      timeout: cdk.Duration.seconds(30)    }).addRetry({      errors: ['PaymentProcessingException'],      interval: cdk.Duration.seconds(3),      maxAttempts: 2,      backoffRate: 2.0    });
    const confirmOrderTask = new tasks.LambdaInvoke(this, 'ConfirmOrderTask', {      lambdaFunction: confirmOrder,      outputPath: '$.Payload'    });
    // Compensation task'lar    const cancelOrderTask = new tasks.LambdaInvoke(this, 'CancelOrderTask', {      lambdaFunction: cancelOrder,      outputPath: '$.Payload'    });
    const releaseInventoryTask = new tasks.LambdaInvoke(this, 'ReleaseInventoryTask', {      lambdaFunction: releaseInventory,      outputPath: '$.Payload'    });
    const refundPaymentTask = new tasks.LambdaInvoke(this, 'RefundPaymentTask', {      lambdaFunction: refundPayment,      outputPath: '$.Payload'    });
    // Compensation logic ile saga orchestration    // Compensation chain'i backwards oluştur    const compensatePayment = refundPaymentTask      .next(releaseInventoryTask)      .next(cancelOrderTask)      .next(new sfn.Fail(this, 'OrderFailed', {        error: 'OrderProcessingFailed',        cause: 'Payment processing failed, all steps compensated'      }));
    const compensateInventory = releaseInventoryTask      .next(cancelOrderTask)      .next(new sfn.Fail(this, 'InventoryFailed', {        error: 'InventoryReservationFailed',        cause: 'Inventory reservation failed, order cancelled'      }));
    // Compensation için catch block'larla forward flow oluştur    const sagaDefinition = createOrderTask      .next(reserveInventoryTask        .addCatch(compensateInventory, {          errors: ['States.ALL'],          resultPath: '$.inventoryError'        })      )      .next(processPaymentTask        .addCatch(compensatePayment, {          errors: ['States.ALL'],          resultPath: '$.paymentError'        })      )      .next(confirmOrderTask)      .next(new sfn.Succeed(this, 'OrderSuccess'));
    // Saga state machine oluştur    const orderSaga = new sfn.StateMachine(this, 'OrderSaga', {      definition: sagaDefinition,      stateMachineType: sfn.StateMachineType.STANDARD,      timeout: cdk.Duration.minutes(5),      tracingEnabled: true    });
    new cdk.CfnOutput(this, 'OrderSagaArn', {      value: orderSaga.stateMachineArn,      description: 'Order Saga State Machine ARN'    });  }}

Bu infrastructure, doğru compensation chain'leriyle complete bir order processing saga setup'ı yapıyor. Her step'in transient error'lar için retry configuration'ı olduğuna ve compensation flow'larının reverse order'da build edildiğine dikkat et; bu tamamlanmış step'leri doğru şekilde geri almak için kritik.

Idempotent Operation'ları Implement Etmek

Saga'larda idempotency tartışmasız gerekli. Step'ler retry'lar, failure'lar veya network sorunları nedeniyle birden fazla kez execute olabilir. Düzgün idempotent operation'ları nasıl implement edeceğini göstereyim:

typescript
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';import { DynamoDBDocumentClient, UpdateCommand, GetCommand } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({});const docClient = DynamoDBDocumentClient.from(client);
interface ReserveInventoryEvent {  orderId: string;  productId: string;  quantity: number;  transactionId: string; // Idempotency key}
export const handler = async (event: ReserveInventoryEvent) => {  console.log('Reserving inventory', { orderId: event.orderId, productId: event.productId });
  try {    // Idempotent check: Bu transaction zaten tamamlandı mı?    const existingReservation = await docClient.send(      new GetCommand({        TableName: process.env.INVENTORY_TABLE!,        Key: { productId: event.productId }      })    );
    const reservation = existingReservation.Item?.reservations?.[event.transactionId];    if (reservation?.status === 'RESERVED') {      console.log('Inventory already reserved for this transaction', {        transactionId: event.transactionId      });      return {        success: true,        message: 'Idempotent: Already reserved',        reservationId: event.transactionId      };    }
    // Conditional expression ile atomic update (semantic lock)    const result = await docClient.send(      new UpdateCommand({        TableName: process.env.INVENTORY_TABLE!,        Key: { productId: event.productId },        UpdateExpression:          'SET availableQuantity = availableQuantity - :qty, ' +          'reservedQuantity = reservedQuantity + :qty, ' +          'reservations.#txId = :reservation',        ConditionExpression: 'availableQuantity >= :qty',        ExpressionAttributeNames: {          '#txId': event.transactionId        },        ExpressionAttributeValues: {          ':qty': event.quantity,          ':reservation': {            orderId: event.orderId,            quantity: event.quantity,            status: 'RESERVED',            timestamp: Date.now(),            expiresAt: Date.now() + (15 * 60 * 1000) // 15 dakika timeout          }        },        ReturnValues: 'ALL_NEW'      })    );
    console.log('Inventory reserved successfully', {      orderId: event.orderId,      reservationId: event.transactionId    });
    return {      success: true,      reservationId: event.transactionId,      availableQuantity: result.Attributes?.availableQuantity    };
  } catch (error: any) {    if (error.name === 'ConditionalCheckFailedException') {      console.error('Insufficient inventory', {        productId: event.productId,        requested: event.quantity      });      throw new Error('InsufficientInventory');    }
    console.error('Failed to reserve inventory', error);    throw error;  }};

Başlangıçtaki idempotency check, bu function aynı transactionId ile birden fazla kez execute olursa side effect olmadan aynı sonucu döndürmesini sağlıyor. Conditional expression atomic bir "semantic lock" sağlıyor; sadece yeterli quantity varsa stok rezerve ediyor.

Compensation: Stok Serbest Bırakma

typescript
export const releaseInventoryHandler = async (event: ReserveInventoryEvent) => {  console.log('Releasing inventory reservation', {    orderId: event.orderId,    transactionId: event.transactionId  });
  try {    const reservation = await docClient.send(      new GetCommand({        TableName: process.env.INVENTORY_TABLE!,        Key: { productId: event.productId }      })    );
    const existingReservation = reservation.Item?.reservations?.[event.transactionId];
    if (!existingReservation) {      console.log('Idempotent: Reservation already released or never existed', {        transactionId: event.transactionId      });      return { success: true, message: 'Already released' };    }
    // Conditional check ile atomic release    await docClient.send(      new UpdateCommand({        TableName: process.env.INVENTORY_TABLE!,        Key: { productId: event.productId },        UpdateExpression:          'SET availableQuantity = availableQuantity + :qty, ' +          'reservedQuantity = reservedQuantity - :qty ' +          'REMOVE reservations.#txId',        ConditionExpression: 'attribute_exists(reservations.#txId)',        ExpressionAttributeNames: {          '#txId': event.transactionId        },        ExpressionAttributeValues: {          ':qty': existingReservation.quantity        }      })    );
    console.log('Inventory released successfully', { transactionId: event.transactionId });    return { success: true };
  } catch (error: any) {    if (error.name === 'ConditionalCheckFailedException') {      // Reservation zaten released - idempotent success      return { success: true, message: 'Already released' };    }    throw error;  }};

Compensation'ın da idempotent olduğuna dikkat et. Reservation yoksa success dönüyoruz; istenen end state zaten sağlanmış.

Idempotency ile Ödeme İşleme

Ödeme işleme de aynı prensipleri takip etmeli: idempotency key (transactionId), conditional write'lar ve retry-safe logic. Payment provider'ın idempotency key'lerini kullan; aynı key ile tekrarlanan çağrılar duplicate charge oluşturmamalı.

EventBridge ile Choreography Implementasyonu

Daha basit flow'lar için choreography daha iyi decoupling sağlayabilir. Event-driven saga koordinasyonunu nasıl implement edeceğini göstereyim:

typescript
import { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';
// Order Service: Sipariş oluşturur ve OrderCreated event'i publish ederexport const createOrderHandler = async (event: any) => {  const orderId = `ord_${Date.now()}`;
  // Database'de sipariş oluştur  await saveOrder({    orderId,    customerId: event.customerId,    items: event.items,    status: 'PENDING'  });
  // OrderCreated event'i publish et  const eventBridge = new EventBridgeClient({});  await eventBridge.send(    new PutEventsCommand({      Entries: [{        Source: 'order.service',        DetailType: 'OrderCreated',        Detail: JSON.stringify({          orderId,          customerId: event.customerId,          items: event.items,          totalAmount: event.totalAmount,          timestamp: Date.now()        })      }]    })  );
  return { orderId, status: 'PENDING' };};
// Inventory Service: OrderCreated'i dinler, stok rezerve ederexport const reserveInventoryOnOrderHandler = async (event: any) => {  const { orderId, items } = event.detail;
  try {    const reservationResult = await reserveInventoryForOrder(orderId, items);
    const eventBridge = new EventBridgeClient({});    await eventBridge.send(      new PutEventsCommand({        Entries: [{          Source: 'inventory.service',          DetailType: 'InventoryReserved',          Detail: JSON.stringify({            orderId,            reservationId: reservationResult.reservationId,            items,            timestamp: Date.now()          })        }]      })    );
  } catch (error: any) {    // Failure event'i publish et    const eventBridge = new EventBridgeClient({});    await eventBridge.send(      new PutEventsCommand({        Entries: [{          Source: 'inventory.service',          DetailType: 'InventoryReservationFailed',          Detail: JSON.stringify({            orderId,            reason: error.message,            timestamp: Date.now()          })        }]      })    );  }};
// Payment Service: InventoryReserved'i dinler, ödemeyi işlerexport const processPaymentOnInventoryReservedHandler = async (event: any) => {  const { orderId } = event.detail;
  try {    const order = await getOrder(orderId);    const paymentResult = await processPayment({      orderId,      customerId: order.customerId,      amount: order.totalAmount    });
    const eventBridge = new EventBridgeClient({});    await eventBridge.send(      new PutEventsCommand({        Entries: [{          Source: 'payment.service',          DetailType: 'PaymentCompleted',          Detail: JSON.stringify({            orderId,            paymentId: paymentResult.paymentId,            transactionId: paymentResult.transactionId,            timestamp: Date.now()          })        }]      })    );
  } catch (error: any) {    // Failure event - compensation'ı trigger eder    const eventBridge = new EventBridgeClient({});    await eventBridge.send(      new PutEventsCommand({        Entries: [{          Source: 'payment.service',          DetailType: 'PaymentFailed',          Detail: JSON.stringify({            orderId,            reason: error.message,            timestamp: Date.now()          })        }]      })    );  }};

Choreography'de her servis ilgili event'leri dinlemek ve yeni event'ler publish etmekten sorumlu. Compensation da aynı event mekanizması üzerinden gerçekleşiyor; bir servis failure event'i publish ettiğinde, diğer servisler compensating transaction'larını execute ederek react ediyor.

Semantic Locking ile Isolation

Saga'lar geleneksel transaction isolation'dan yoksun, bu da concurrent saga conflict'lerine yol açabilir. Semantic locking application-level isolation sağlıyor:

typescript
interface SagaLock {  sagaId: string;  lockedAt: number;  expiresAt: number;}
export const acquireSagaLock = async (orderId: string, sagaId: string) => {  const lockDuration = 5 * 60 * 1000; // 5 dakika  const now = Date.now();
  try {    await docClient.send(      new UpdateCommand({        TableName: process.env.ORDERS_TABLE!,        Key: { orderId },        UpdateExpression:          'SET sagaLock = :lock, #status = :processing',        ConditionExpression:          'attribute_not_exists(sagaLock) OR sagaLock.expiresAt < :now',        ExpressionAttributeNames: {          '#status': 'status'        },        ExpressionAttributeValues: {          ':lock': {            sagaId,            lockedAt: now,            expiresAt: now + lockDuration          },          ':processing': 'PROCESSING',          ':now': now        }      })    );
    console.log('Saga lock acquired', { orderId, sagaId });    return true;
  } catch (error: any) {    if (error.name === 'ConditionalCheckFailedException') {      console.warn('Saga lock already held by another saga', { orderId });      throw new Error('SagaLockConflict');    }    throw error;  }};

Bu semantic lock, iki saga'nın aynı siparişi concurrent olarak modify etmesini önlüyor. Lock, saga crash olduğunda lock'u release edememe durumlarını handle etmek için expiration time içeriyor.

Maliyet Analizi ve Trade-off'lar

Maliyet etkilerini anlamak doğru yaklaşımı seçmene yardımcı oluyor.

Step Functions Orchestration Maliyetleri

100,000 sipariş/ay için 8-step saga:

  • Total state transition: 800,000
  • Maliyet: (800,000 / 1,000) × 0.025=0.025 = **20/ay**
  • Başarısız saga'lar (4-step compensation ile %5): ~20,000 transition = +$0.50/ay
  • Toplam: ~$20.50/ay

EventBridge Choreography Maliyetleri

100,000 sipariş/ay için sipariş başına 4 event:

  • Total event: 400,000
  • Maliyet: (400,000 / 1,000,000) × 1.00=1.00 = **0.40/ay**
  • Compensation ile başarısız siparişler: +$0.03/ay
  • Toplam: ~$0.43/ay

Gerçek Trade-off

Choreography önemli ölçüde daha ucuz ama daha yüksek development ve debugging complexity'siyle geliyor. Orchestration daha pahalı ama daha iyi visibility ve daha kolay troubleshooting sağlıyor. Çoğu production sistemde, orchestration'ın improved observability'si ek maliyete değiyor.

Yaygın Hatalar ve Çözümler

Hata 1: Non-Idempotent Operation'lar

Problem: Retry'da ödeme birden fazla kez alınıyor.

Çözüm: Her zaman idempotency check'leri implement et ve provider idempotency key'leri kullan.

Hata 2: Eksik Compensation Chain'leri

Problem: Sadece son step compensate ediliyor, daha önceki step'ler inconsistent state'te kalıyor.

Çözüm: Tüm compensation'ları reverse order'da chain'le. Her catch block önceki tüm step'leri compensate etmeli.

Hata 3: Compensation Failure'larını Ignore Etmek

Problem: Compensation başarısız oluyor, saga askıda kalıyor.

Çözüm: Compensation'lar için aggressive retry (10+ attempt) ve manual intervention için dead-letter queue.

Hata 4: Timeout Çok Kısa

Problem: Timeout compensation'ı tetikliyor ama operation aslında başarılı olmuştu.

Çözüm: Buffer ile gerçekçi timeout'lar belirle. Compensate etmeden önce gerçek state'i doğrula.

Hata 5: Choreography'de Saga State Takibi Yok

Problem: Hangi siparişlerin compensation'da olduğu belirlenemiyor.

Çözüm: Observability için choreography'de bile saga state'ini persist et.

Önemli Çıkarımlar

  1. Kompleks flow'lar için orchestration seç (>4 servis), basit linear flow'lar için choreography
  2. Idempotency kritik; her saga step doğru key'lerle idempotent olmalı
  3. Reverse order'da compensate et; compensation'ları idempotent ve retryable yap
  4. Semantic locking kullan concurrent saga conflict'leri önlemek için
  5. Gerçekçi timeout'lar belirle ve compensate etmeden önce state'i verify et
  6. İlk günden observability implement et; structured logging, metrics, tracing
  7. Saga'ları basit tut (3-5 step); kompleks workflow'lar için birden fazla saga chain'le
  8. Saga state'i persist et choreography'de bile debugging için
  9. Compensation'ları aggressively retry et manual intervention gerektiren failure'lar için DLQ ile
  10. Maliyet vs complexity'yi düşün; orchestration daha iyi visibility sağlar ama daha pahalı

Saga pattern, microservices'te distributed transaction'lar için robust bir çözüm sağlıyor. Orchestration ile choreography arasındaki trade-off'ları anlayarak, doğru idempotency ve compensation implement ederek ve güçlü observability kurarak, servis sınırları arasında consistency'yi koruyan güvenilir dağıtık sistemler geliştirebilirsin.

Helper Functions

Örneklerde referans verilen basitleştirilmiş helper fonksiyonları (saveOrder, getOrder, reserveInventoryForOrder, releaseInventory, processPayment, updateOrderStatus) – tam implementasyon için EN versiyonuna bakın.

İlgili Yazılar