İçeriğe atla

Multi-Account AWS Mimarisi: Ölçeklenebilir Event-Driven Sistemler

Dayanıklı event-driven sistemler için multi-account AWS pattern'leri: hesap yapısı, EventBridge routing ve servisler arası iletişim.

Ayhan Sipahi Ayhan Sipahi

Single-Account Mimarisinin Sınırları#

Multi-account AWS mimarisi, organizasyonlar belirli ölçek ve karmaşıklık eşiklerine ulaştığında gerekli hale gelir. Varsayılan olarak benimsenmeye değer yapı şu: servis takımı başına bir hesap, aralarında paylaşılan bir EventBridge bus’ı ve merkezi bir orchestrator yerine event choreography.

Dokuz geliştirme takımının aynı AWS hesabına deploy ettiği çok servisli bir platform düşünün. Bu yaklaşım küçük organizasyonlarda işe yarar, ancak ölçek arttıkça kritik sorunlar üretir.

Yaygın Single-Account Anti-Pattern’leri#

Birden fazla takımın aynı AWS hesabını paylaşması kaynak çakışmalarına, güvenlik sorunlarına ve operasyonel karmaşıklığa yol açar. Örnek bir anti-pattern konfigürasyonu:

# Tek hesapta paylaşılan kaynaklar anti-pattern'i
Resources:
  CustomerWebLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: platform-customer-web-api
      Role: !GetAtt SharedLambdaRole.Arn

  OrderProcessingLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: platform-order-processing
      Role: !GetAtt SharedLambdaRole.Arn

  PaymentLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: platform-payment-service
      Role: !GetAtt SharedLambdaRole.Arn

  SharedLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/PowerUserAccess

Bu yaklaşım birkaç sorun yaratır:

  1. Patlama Yarıçapı: Bir takımın kaynak değişiklikleri diğerlerini etkileyebilir
  2. İzin Karmaşıklığı: IAM policy’leri denetlenmesi zor hale gelir
  3. Maliyet Atfı: Takım veya servis başına kaynak kullanımını izlemek zorlaşır
  4. Deployment Çakışmaları: Paylaşılan CI/CD pipeline’lar darboğaz yaratır
  5. Güvenlik Sınırları: Tüm takımlar aynı güvenlik çevresinde çalışır

Multi-Account Mimari Deseni#

Multi-account mimarisi, merkezi altyapı üzerinden kontrollü iletişim sağlayarak servisler arasında net sınırlar sunar. Sorumlulukları ayrı AWS hesaplarına böler, sistem bütünlüğünü ise merkezi servisler üzerinden korur.

Etkili bir multi-account yapısı şöyle görünür:

Production Organization Unit

Paylaşılan Servisler

Core Servis Hesapları

Müşteri Yüzlü Hesaplar

Events

Events

Events

Events

Events

Routed Events

Routed Events

Routed Events

Routed Events

Identity Service Hesap: 000000000000

Customer Web Hesap: 111111111111

Mobile Apps Hesap: 222222222222

Partner Portal Hesap: 333333333333

Driver App Hesap: 444444444444

Merchant Dashboard Hesap: 555555555555

Event Bus Hesap: 121212121212

Order Processing Hesap: 777777777777

Delivery Orchestration Hesap: 888888888888

Payment Service Hesap: 999999999999

Inventory Management Hesap: 666666666666

Merkezi Identity Service: Güven Sınırı Deseni#

Multi-account mimariler, güvenlik sınırlarını korurken hesaplar arası iletişime izin vermek için merkezi authentication ve authorization gerektirir. Identity Service, tüm hesaplarda token doğrulama ve izinler için tek doğruluk kaynağı olarak çalışır:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowIdentityServiceToAssumeRole",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::000000000000:role/identity-service-validator"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "${IDENTITY_SERVICE_EXTERNAL_ID}",
          "aws:PrincipalOrgID": "o-quickgrocer123",
          "aws:SourceVpce": "vpce-0abc123def4567890"
        }
      }
    }
  ]
}

Bu merkezileşme tutarlı kimlik doğrulama sağlarken dağıtık JWT validation karmaşıklığını önler. Müşteriye bakan her servis isteklerini merkezi identity service üzerinden doğrular ve güvenlik sınırları korunur.

EventBridge: İletişim Omurgası#

Event-driven mimari, EventBridge’i merkezi iletişim noktası olarak kullanarak servisler arasındaki doğrudan bağımlılıkları ortadan kaldırır. Servisler paylaşılan event bus’a event yayınlar, bus da tanımlı kurallara göre bunları ilgili subscriber’lara yönlendirir.

Order processing için örnek bir EventBridge rule konfigürasyonu:

// Cross-account event routing için CDK kodu
import { Duration } from 'aws-cdk-lib';
import { Rule, EventBus } from 'aws-cdk-lib/aws-events';
import { LambdaFunction } from 'aws-cdk-lib/aws-events-targets';
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';

const orderPlacedRule = new Rule(this, 'OrderPlacedRule', {
  eventBus: EventBus.fromEventBusArn(
    this,
    'CentralEventBus',
    'arn:aws:events:us-east-1:121212121212:event-bus/central-bus'
  ),
  eventPattern: {
    source: ['quickgrocer.customer-web'],
    detailType: ['Order Placed'],
    detail: {
      orderStatus: ['PENDING'],
      paymentMethod: ['CREDIT_CARD', 'DEBIT_CARD', 'APPLE_PAY']
    }
  },
  targets: [
    new LambdaFunction(orderProcessingLambda, {
      retryAttempts: 2,
      deadLetterQueue: orderProcessingDLQ,
      maxEventAge: Duration.hours(2)
    })
  ]
});

// Cross-account event publishing için izinler
const centralBusArn = 'arn:aws:events:us-east-1:121212121212:event-bus/central-bus';
const publishPolicy = new PolicyStatement({
  effect: Effect.ALLOW,
  actions: ['events:PutEvents'],
  resources: [centralBusArn],
  conditions: {
    StringEquals: {
      'events:detail-type': [
        'Order Placed',
        'Order Updated',
        'Order Cancelled'
      ]
    }
  }
});

Event-Driven Veri Akış Desenleri#

Event-driven mimari servisler arası veri akışının dikkatli orkestrasyonunu gerektirir. Abonelik yükseltme iş akışı, event’lerin birden fazla hesapta durum değişikliklerini nasıl koordine ettiğini gösterir.

Abonelik yükseltme event akışı şöyle işler:

Order ProcessingInventory MgmtSubscription ServicePayment ServiceEvent BusIdentity ServiceCustomer WebOrder ProcessingInventory MgmtSubscription ServicePayment ServiceEvent BusIdentity ServiceCustomer WebEvent choreography ile eventual consistencyValidate user permissionsJWT with subscription scopesSubscriptionUpgradeRequestedRoute to payment processingRoute to subscription servicePaymentProcessed (success)SubscriptionActivatedUpdate inventory allocationsEnable priority orderingUpdate user interface state

Cross-Service Veri Senkronizasyonu#

Abonelik durumu, hesaplar arasında doğrudan veritabanı erişimi olmadan birden fazla serviste kullanılabilmeli. Çözüm, local cache’lerle birlikte event-sourced state replication.

// Subscription Service - authoritative event yayınlar
export class SubscriptionService {
  async upgradeSubscription(userId: string, planId: string) {
    const subscription = await this.subscriptionRepo.create({
      userId, planId, status: 'ACTIVE',
      startDate: new Date(), features: this.getFeaturesByPlan(planId)
    });
    await this.eventPublisher.publish({
      source: 'quickgrocer.subscription-service',
      detailType: 'Subscription Activated',
      detail: { userId, subscriptionId: subscription.id, plan: { id: planId, features: ['priority_delivery'] } }
    });
    return subscription;
  }
}

// Order Processor - local subscription cache ile
export class OrderProcessor {
  private subscriptionCache = new Map<string, SubscriptionInfo>();
  @EventHandler('Subscription Activated')
  async onSubscriptionActivated(event: SubscriptionEvent) {
    this.subscriptionCache.set(event.detail.userId, { plan: event.detail.plan, features: event.detail.plan.features });
  }
  async processOrder(order: Order) {
    const subscription = this.subscriptionCache.get(order.userId);
    if (subscription?.features.includes('priority_delivery')) order.priority = 'HIGH';
  }
}

Event Choreography vs Orchestration#

Tek bir servisin tüm akışı kontrol ettiği orchestration deseni, sıkı coupling ve tek bir arıza noktası yaratır. Kaçınılması gereken yaklaşım:

// Orchestration anti-pattern'i, bu yaklaşımdan kaçının
export class SubscriptionOrchestrator {
  async upgradeSubscription(userId: string, planId: string) {
    try {
      // Her servis sırayla doğrudan çağrılıyor
      await this.paymentService.processPayment(userId, planId);
      await this.subscriptionService.create(userId, planId);
      await this.inventoryService.allocatePrioritySlots(userId);
      await this.orderService.enablePriorityProcessing(userId);
    } catch (error) {
      // Her adım için karmaşık rollback mantığı gerekir
      await this.rollbackEverything(userId, planId);
    }
  }
}

Choreography, her servisin yalnızca kendi parçasını bilmesiyle daha gevşek coupling ve daha iyi dayanıklılık sağlar:

// Choreography - her servis kendi parçasını bilir
export class PaymentEventHandlers {
  @EventHandler('Subscription Upgrade Requested')
  async handleUpgradeRequest(event: UpgradeEvent) {
    const result = await this.processPayment(event.detail);
    await this.publishEvent('Payment Processed', { userId: event.detail.userId, amount: result.amount });
  }
}
export class SubscriptionEventHandlers {
  @EventHandler('Payment Processed')
  async activateSubscription(event: PaymentEvent) {
    const subscription = await this.create(event.detail.userId);
    await this.publishEvent('Subscription Activated', { userId: event.detail.userId, subscriptionId: subscription.id });
  }
}

Hesap Yapısı ve İzolasyon#

Her takım, net sınırlar ve sorumluluklarla izole AWS hesaplarında çalışır:

# Multi-account organizasyon yapısı
platform-org/
├── production/
  ├── customer-facing/
  ├── customer-web-111111111111/
  ├── mobile-apps-222222222222/
  ├── partner-portal-333333333333/
  ├── driver-app-444444444444/
  └── merchant-dashboard-555555555555/
  ├── core-services/
  ├── inventory-mgmt-666666666666/
  ├── order-processing-777777777777/
  ├── delivery-orchestration-888888888888/
  └── payment-service-999999999999/
  └── shared-services/
  ├── identity-service-000000000000/
  ├── event-bus-121212121212/
  └── monitoring-131313131313/
├── staging/
  └── [production yapısını yansıtır]
└── development/
    └── [geliştirici takımı başına bir hesap]

Multi-Account Mimarinin Faydaları#

1. Takım Özerkliği#

Takımlar koordinasyon yükü olmadan bağımsız deploy edebilir. Farklı takımlar birbirlerini etkilemeden ayrı release döngüleri ve deployment takvimleri yürütebilir.

2. Patlama Yarıçapı Kontrolü#

Kaynak sorunları ve konfigürasyon hataları tek bir hesabın içinde kalır. Bir hesaptaki servis arızası diğer servislere yayılmaz, sistemin geneli ayakta kalmaya devam eder.

3. Net Maliyet Atfı#

Takım veya servis başına ayrılmış hesaplarla maliyet dağılımı doğrudan okunabilir hale gelir:

// Maliyet tahsis etiketleme stratejisi
function applyCostTags(resource: any, teamName: string, serviceName: string): Record<string, string> {
    return {
        'Team': teamName,
        'Service': serviceName,
        'Environment': process.env.ENVIRONMENT || 'dev',
        'CostCenter': TEAM_COST_CENTERS[teamName],
        'Owner': TEAM_LEADS[teamName],
        'CreatedDate': new Date().toISOString(),
        'ManagedBy': 'CDK'
    };
}

// Örnek aylık maliyet dağılımı:
// Customer Web:  $12,450 (%25)
// Mobile Apps:  $8,230  (%17)
// Order Processing:  $15,670 (%32)
// Delivery Orchestration: $7,890 (%16)
// Identity Service:  $4,760  (%10)

4. Güvenlik Sınırları#

Her hesap kendi güvenlik çevresini korur. Compliance gereksinimleri, diğer hesapları etkilemeden yalnızca ilgili hesaplara uygulanabilir:

// Payment service hesap güvenlik baseline'ı.
// Bu stack yalnızca 999999999999 hesabına deploy edilir.
import { Stack } from 'aws-cdk-lib';
import { CfnHub, CfnStandard } from 'aws-cdk-lib/aws-securityhub';

const region = Stack.of(this).region;

// Standard'lar aşağıda tek tek tanımlandığı için hub varsayılanları atlıyor.
const hub = new CfnHub(this, 'SecurityHub', {
  enableDefaultStandards: false
});

// CloudFormation aksi belirtilmedikçe kaynakları paralel oluşturur ve hub
// hazır olmadan bir standard etkinleştirilemez.
const pciDss = new CfnStandard(this, 'PciDss', {
  standardsArn: `arn:aws:securityhub:${region}::standards/pci-dss/v/3.2.1`
});
pciDss.addDependency(hub);

const foundationalSecurity = new CfnStandard(this, 'FoundationalSecurity', {
  standardsArn: `arn:aws:securityhub:${region}::standards/aws-foundational-security-best-practices/v/1.0.0`
});
foundationalSecurity.addDependency(hub);

Zorluklar ve Çözümler#

1. Event Schema Evolution#

Distributed sistemlerde event schema değişikliklerini yönetmek dikkatli versiyonlama stratejileri gerektirir. Schema’lar zaman içinde kaçınılmaz olarak evrilir:

// Versiyon 1
{
  "orderId": "ord-123",
  "customerId": "cust-456",
  "items": ["item-1", "item-2"],
  "total": 45.99
}

Birden fazla iterasyon ve gereksinim değişikliği sonrası:

// Versiyon 7, altı tur ekleme sonrası
{
  "orderId": "ord-123",
  "customerId": "cust-456",
  "customerIdV2": "usr_cust-456",  // Yeni ID formatı
  "items": ["item-1", "item-2"],  // Deprecated, itemsV2 kullan
  "itemsV2": [
    {
      "id": "item-1",
      "quantity": 2,
      "price": 12.99,
      "modifiers": []  // v4'te eklendi
    }
  ],
  "total": 45.99,  // v5'te deprecated
  "totalAmount": {  // v5'te eklendi
    "value": 45.99,
    "currency": "USD"
  },
  "metadata": {  // v6'da eklendi
    "source": "mobile-app",
    "version": "2.3.1"
  }
}

Düzgün schema yönetimi olmadan consumer’lar hızla karmaşıklaşır:

// Schema registry olmadan versiyon yönetimi
export const handleOrderPlaced = async (event: any) => {
  // Hangi versiyonla uğraştığımızı kontrol et
  const version = event.metadata?.schemaVersion ||
                  (event.customerIdV2 ? 7 :
                   event.totalAmount ? 5 :
                   event.items?.[0]?.modifiers ? 4 : 1);

  switch(version) {
    case 1:
    case 2:
    case 3:
      return handleLegacyOrder(event);
    case 4:
      return handleV4Order(migrateV4ToV7(event));
    case 5:
    case 6:
      return handleV5Order(migrateV5ToV7(event));
    case 7:
      return handleCurrentOrder(event);
    default:
      // Bilinmeyen versiyonları kontrollü şekilde ele al
      console.error('Bilinmeyen order versiyonu:', event);
      throw new Error('Bilinmeyen schema versiyonu');
  }
};

2. Cross-Account Observability#

Bir isteği birden fazla AWS hesabı boyunca takip etmek kapsamlı observability altyapısı gerektirir. Distributed tracing burada zorunlu hale gelir:

Yaygın debugging zorlukları:

  • Gecikme sorunu herhangi bir hesapta doğabilir
  • Event routing hatalarının izini sürmek zordur
  • Servis bağımlılıkları birden fazla hesaba yayılır
  • Geleneksel monitoring araçları hesaplar arası görünürlük sunmaz

Distributed tracing bu zorlukları çözer:

// Distributed tracing implementasyonu
import { trace, context, propagation, SpanStatusCode } from '@opentelemetry/api';

const tracer = trace.getTracer('quickgrocer-order-service', '1.0.0');

export const processOrder = async (event: any) => {
  // EventBridge event'inden trace context'i çıkar
  const traceParent = event.detail?.traceContext?.traceparent;
  const traceState = event.detail?.traceContext?.tracestate;

  // Upstream service'ten trace'i devam ettir
  const extractedContext = propagation.extract(context.active(), {
    traceparent: traceParent,
    tracestate: traceState
  });

  return context.with(extractedContext, () => {
    const span = tracer.startSpan('process-order', {
      attributes: {
        'order.id': event.detail.orderId,
        'order.account': process.env.AWS_ACCOUNT_ID,
        'order.region': process.env.AWS_REGION,
        'order.service': 'order-processing'
      }
    });

    try {
      // Siparişi işle
      const result = await actuallyProcessOrder(event);
      span.setStatus({ code: SpanStatusCode.OK });
      return result;
    } catch (error) {
      span.recordException(error);
      span.setStatus({
        code: SpanStatusCode.ERROR,
        message: error.message
      });
      throw error;
    } finally {
      span.end();
    }
  });
};

3. Maliyet Optimizasyonu#

Multi-account mimariler, dikkatli yönetilmesi gereken ek maliyetler getirir. Cross-account data transfer, event işleme ve kaynak tekrarı faturayı büyütür:

# Dokuz hesaplı bir kurulum için örnek aylık ek maliyet
EventBridge Events:  $345  # Milyon başına $1.00 üzerinden 345 milyon custom event
Cross-AZ Data Transfer:  $2,100  # Event'ler regional kalmak yerine AZ'ler arası geçiyor
NAT Gateway (9 hesap):  $315  # Hesap başına ayda ~$35
CloudWatch Logs:  $4,500  # Varsayılan retention, log seviyesi filtresi yok
Secrets Manager:  $1,800  # Aynı secret'lar her hesapta kopyalanıyor
Parameter Store API calls:  $890  # Cache yok, her invocation yeniden çekiyor

Toplam:  $9,950

Maliyet optimizasyon stratejileri:

// Önce: Her servis her istekte secret'ları çekiyor
const getSecret = async (secretName: string) => {
  const client = new SecretsManagerClient({});
  const response = await client.send(
    new GetSecretValueCommand({ SecretId: secretName })
  );
  return response.SecretString;
};

// Sonra: TTL ile caching
class SecretCache {
  private cache = new Map<string, {value: string, expiry: number}>();
  private ttl = 3600000; // 1 saat

  async getSecret(secretName: string): Promise<string> {
    const cached = this.cache.get(secretName);
    if (cached && cached.expiry > Date.now()) {
      return cached.value;
    }

    const client = new SecretsManagerClient({});
    const response = await client.send(
      new GetSecretValueCommand({ SecretId: secretName })
    );

    this.cache.set(secretName, {
      value: response.SecretString!,
      expiry: Date.now() + this.ttl
    });

    return response.SecretString!;
  }
}

// Caching, Secrets Manager API çağrılarını belirgin ölçüde azaltır

Operasyonel İzleme Desenleri#

Monitoring burada monolitte olduğundan daha fazla ağırlık taşır, çünkü duran bir event bus sağlıklı raporlamaya devam ederken hiçbir teslimat yapılmaz. Tek bir routing kesintisi aynı anda tüm subscriber’lara ulaşır.

Yaygın arıza modları:

  • Devre dışı bırakılmış event routing kuralları
  • Yanlış yapılandırılmış event pattern’leri
  • Cross-account izin sorunları
  • Servis throttling ve limitleri

Kapsamlı monitoring bu sorunları önler:

// Event bus sağlığı için otomatik monitoring
const eventBusMonitor = new Function(this, 'EventBusMonitor', {
  runtime: Runtime.NODEJS_22_X,
  handler: 'monitor.handler',
  code: Code.fromAsset('lambda/event-bus-monitor'),
  environment: {
    EXPECTED_EVENTS_PER_MINUTE: '1000',
    ALERT_THRESHOLD: '100',
    SLACK_WEBHOOK: process.env.SLACK_WEBHOOK
  }
});

// Her dakika çalıştır
new Rule(this, 'MonitorSchedule', {
  schedule: Schedule.rate(Duration.minutes(1)),
  targets: [new LambdaFunction(eventBusMonitor)]
});

// Gerçek monitoring mantığı
export const handler = async () => {
  const cloudWatch = new CloudWatchClient({});

  // Son dakikada yayınlanan event'leri kontrol et
  const metrics = await cloudWatch.send(new GetMetricStatisticsCommand({
    Namespace: 'AWS/Events',
    MetricName: 'SuccessfulRuleMatches',
    StartTime: new Date(Date.now() - 120000),  // 2 dakika önce
    EndTime: new Date(),
    Period: 60,
    Statistics: ['Sum']
  }));

  const eventCount = metrics.Datapoints?.[0]?.Sum || 0;

  if (eventCount < parseInt(process.env.ALERT_THRESHOLD!)) {
    // On-call mühendisi çağır
    await sendSlackAlert({
      text: `[ALERT] EVENT BUS KRİTİK: Son dakikada sadece ${eventCount} event!`,
      color: 'danger'
    });

    // Otomatik iyileşme denemesi
    await enableAllRules();
  }
};

Erken Benimsemeye Değer Uygulamalar#

Dört uygulama, ilk günde benimsendiğinde sonradan eklenmesinden çok daha ucuza gelir:

1. Schema Registry’yi Erken Kurun#

EventBridge Schema Registry kontratı saklar ve versiyonlar, ancak PutEvents sırasında hatalı event’leri reddetmez. Kontratın event’in yayınlandığı yerde uygulanması için client-side doğrulamayla birlikte kullanın:

// Kontratı kaydet, sonra yayınlamadan önce ona karşı doğrula
import { SchemasClient, CreateSchemaCommand } from '@aws-sdk/client-schemas';
import { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';
import Ajv from 'ajv';

const schemas = new SchemasClient({});
const eventBridge = new EventBridgeClient({});
const ajv = new Ajv();

// Schema'yı versiyonlama ile tanımla
const orderSchema = {
  openapi: '3.0.0',
  info: {
    version: '1.0.0',
    title: 'OrderPlaced'
  },
  paths: {},
  components: {
    schemas: {
      OrderPlaced: {
        type: 'object',
        required: ['orderId', 'customerId', 'items', 'totalAmount'],
        properties: {
          orderId: { type: 'string', pattern: '^ord-[0-9a-f]{8}$' },
          customerId: { type: 'string', pattern: '^cust-[0-9a-f]{8}$' },
          items: {
            type: 'array',
            items: {
              $ref: '#/components/schemas/OrderItem'
            }
          },
          totalAmount: {
            $ref: '#/components/schemas/Money'
          }
        }
      }
    }
  }
};

// Schema'yı deploy zamanında bir kez kaydet
await schemas.send(new CreateSchemaCommand({
  RegistryName: 'quickgrocer-events',
  SchemaName: 'OrderPlaced',
  Type: 'OpenApi3',
  Content: JSON.stringify(orderSchema)
}));

// Yayınlamadan önce, registry'den üretilen JSON Schema ile doğrula
const validateOrderPlaced = ajv.compile(orderPlacedJsonSchema);

const validateAndPublish = async (entry: { Source: string; DetailType: string; Detail: string }) => {
  if (!validateOrderPlaced(JSON.parse(entry.Detail))) {
    throw new Error(`Schema doğrulama başarısız: ${ajv.errorsText(validateOrderPlaced.errors)}`);
  }
  return await eventBridge.send(new PutEventsCommand({ Entries: [entry] }));
};

2. Observability-First Mimari#

Monitoring ve tracing mimariye baştan gömülmeli:

// Kapsamlı observability implementasyonu
class InstrumentedEventPublisher {
  private metrics: MetricsClient;
  private tracer: Tracer;

  async publish(event: Event): Promise<void> {
    const span = this.tracer.startSpan('event.publish');
    const timer = this.metrics.startTimer('event.publish.duration');

    try {
      // Event'e trace context ekle
      event.traceContext = {
        traceparent: span.spanContext().traceId,
        tracestate: span.spanContext().traceState
      };

      await this.eventBridge.putEvents({
        Entries: [{
          ...event,
          Detail: JSON.stringify({
            ...JSON.parse(event.Detail),
            _metadata: {
              timestamp: Date.now(),
              account: process.env.AWS_ACCOUNT_ID,
              service: process.env.SERVICE_NAME,
              version: process.env.SERVICE_VERSION,
              traceId: span.spanContext().traceId
            }
          })
        }]
      });

      this.metrics.increment('event.published', {
        type: event.DetailType,
        source: event.Source
      });

    } catch (error) {
      this.metrics.increment('event.publish.error', {
        type: event.DetailType,
        error: error.name
      });
      span.recordException(error);
      throw error;
    } finally {
      timer.end();
      span.end();
    }
  }
}

3. Otomatik Hesap Yönetimi#

Manuel hesap oluşturma ölçeklenmez. Otomatik account vending zorunlu hale gelir:

// Otomatik account vending implementasyonu
import { OrganizationsClient, CreateAccountCommand } from '@aws-sdk/client-organizations';

class AccountVendingMachine {
  private organizations = new OrganizationsClient({});

  async createTeamAccount(team: TeamConfig): Promise<AWSAccount> {
    // 1. Hesabı talep et. CreateAccount asenkrondur, bu yüzden durum
    //    IN_PROGRESS'ten çıkana kadar DescribeCreateAccountStatus ile bekle.
    const { CreateAccountStatus } = await this.organizations.send(new CreateAccountCommand({
      AccountName: `quickgrocer-${team.name}-${team.environment}`,
      Email: `aws+${team.name}+${team.environment}@quickgrocer.com`,
      RoleName: 'OrganizationAccountAccessRole'
    }));
    const account = await this.waitForAccount(CreateAccountStatus!.Id!);

    // 2. Doğru OU'ya taşı ve baseline servisleri aç
    await this.moveToOrganizationalUnit(account.id, this.getOUForTeam(team));
    await this.enableBaselineServices(account.id, {
      cloudTrail: true,
      config: true,
      securityHub: true,
      guardDuty: true,
      budgetLimit: team.monthlyBudget
    });

    // 3. Takıma özel SCP'leri uygula
    await this.applyServiceControlPolicies(account.id, team.permissions);

    // 4. Cross-account rolleri kur
    await this.setupCrossAccountRoles(account.id, {
      identityServiceRole: 'arn:aws:iam::000000000000:role/identity-validator',
      eventBusRole: 'arn:aws:iam::121212121212:role/event-publisher'
    });

    // 5. Baseline altyapıyı deploy et
    await this.deployBaseline(account.id, {
      vpcCidr: this.allocateVpcCidr(team),
      eventBusArn: 'arn:aws:events:us-east-1:121212121212:event-bus/central-bus',
      logGroupRetention: 30
    });

    return account;
  }
}

4. Multi-Region Mimari Planlaması#

Bölgesel genişleme, tasarımın erken aşamasında düşünülmeli:

// Multi-region mimari tasarımı
const multiRegionStack = new Stack(app, 'MultiRegionInfra', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION
  }
});

// Birden fazla bölgeye deploy et
['us-east-1', 'eu-west-1', 'ap-southeast-1'].forEach(region => {
  new RegionalStack(app, `Regional-${region}`, {
    env: { region },
    eventBusArn: `arn:aws:events:${region}:121212121212:event-bus/central-bus`,
    // Regional event routing
    eventRouting: {
      primary: region,
      failover: getFailoverRegion(region)
    }
  });
});

Multi-Account Ne Zaman Karşılığını Verir#

Varsayılan, bağımsız deployment temposu paylaşılan hesabın kolaylığından daha önemli hale geldiğinde geçerlidir: servis takımı başına bir hesap, aralarında paylaşılan bir EventBridge bus’ı ve merkezi bir orchestrator yerine choreography. Bu noktadan sonra tek bir hesabın koordinasyon maliyeti, ek IAM, ağ ve observability düzeneğini işletme maliyetini aşar.

Bu eşiğin altındaysanız varsayılanı bırakın. Tek bir ürünü çıkaran iki üç takımın cross-account rollere, account vending machine’e veya “bu istek nereye gitti” sorusunu yanıtlamak için distributed tracing’e ihtiyacı yok. Ortam başına tek hesapla başlayın, bir takım ilk kez başka bir takımın deploy’unu blokladığında bölün.

Hangi tarafta olursanız olun, event kontratlarını baştan açık tutun. Schema versiyonlama ve trace propagation, her consumer’ın etrafında sabit kod yazdığı iki parçadır; bunu bir kez yaptıklarında değişiklik artık bir kod değişikliği değil, bir migration olur.

Kaynaklar#

İlgili yazılar