İçeriğe atla

2025-10-29

TypeScript için CloudEvents SDK: Serverless Mimarilerde Event Standardizasyonu

CloudEvents spesifikasyonu ve TypeScript SDK'sını serverless projelerde kullanmak için pratik bir kılavuz. AWS Lambda, EventBridge ve diğer event-driven sistemlerde standardize edilmiş eventler oluşturmayı, parse etmeyi ve validate etmeyi öğrenin.

Event-driven mimarilerde her event kaynağı event’leri farklı şekilde tanımlama eğilimindedir: bir Lambda { userId: string } beklerken, diğeri { user_id: string }, üçüncüsü ise { sub: string } kullanır. Bu heterojenliğin maliyeti, kaynak sayısıyla büyüyen entegrasyon kodu ve sistemler arasında event’leri ilişkilendiremeyen gözlemleme araçlarıdır. Standartlaştırılmış bir event zarfı her iki sorunu da çözer; karşılığında her üreticinin aynı şemayı benimsemesini gerektirir.

CloudEvents, bu zarf için CNCF spesifikasyonudur. Bu yazı, serverless projelerde CloudEvents TypeScript SDK’sını ele alır: event şeklini ve zorunlu niteliklerini, AWS Lambda üzerinde üretici ve tüketici desenlerini, taşıma bağlamalarını (HTTP, SQS, SNS) ve özel event formatlarından standart formatlara geçiş yolunu kapsar.

Event Standardizasyonu Neden Önemli

Event-driven sistemler loose coupling ile gelişir, ancak her producer kendi event formatını icat ettiğinde sorunlar yaşar. Zorluklar hızla birikiyor:

  • Her event source için özel parsing logic
  • Farklı servisler arası ortak tooling yok
  • Eventler beklentilere uymadığında debug zorluğu
  • Event producerları değiştiğinde migration sürtünmesi

CloudEvents bu sorunları, platformlar, diller ve protokoller arası çalışan ortak bir envelope formatı ile çözüyor. EventBridge, Kafka veya Kinesis kullanıyor olsan da aynı specversion, type ve source attributeları tooling entegrasyonunu kolaylaştırır.

CloudEvents Spesifikasyonunu Anlamak

CloudEvents, eventleri tanımlayan standart metadata attributelarını belirliyor:

{
  "specversion": "1.0",  // CloudEvents versiyonu
  "type": "com.example.order.created",  // Event tipi
  "source": "/orders/service",  // Event producer
  "id": "A234-1234-1234",  // Unique event ID
  "time": "2025-10-29T12:00:00Z",  // Ne zaman oldu
  "datacontenttype": "application/json",  // Payload formatı
  "data": {  // Asıl payload
    "orderId": "12345",
    "amount": 99.99
  }
}

Bu yapı event metadatasını (kim, ne, ne zaman) event datadan (payload) ayırarak, tüm payloadı parse etmeden eventleri route etmeyi, filtrelemeyi ve işlemeyi kolaylaştırıyor.

CloudEvent

Type'a gore filtrele

Source'a gore filtrele

Attribute'lara gore filtrele

Event Producer

Event Router

Lambda Handler 1

Lambda Handler 2

Lambda Handler 3

CloudEvents SDK Kurulumu

JavaScript SDK, TypeScript tanımlamaları sağlıyor ve Node.js 18+ ile çalışıyor (Node.js 22 öneriliyor):

npm install cloudevents

SDK hafif (harici HTTP dependency’si yok) ve CloudEvents v1.0 spesifikasyonunu destekliyor.

TypeScript’te CloudEvent Oluşturma

SDK, tam TypeScript desteği olan bir CloudEvent class’ı sağlıyor:

import { CloudEvent } from 'cloudevents';

// Basit event oluşturma
const event = new CloudEvent({
  type: 'com.example.user.created',
  source: '/users/service',
  data: {
    userId: '12345',
    email: '[email protected]',
    createdAt: new Date().toISOString()
  }
});

console.log(event.id);  // Otomatik generate edilen UUID
console.log(event.time); // Otomatik generate edilen timestamp

Event Data için Type Safety

Event datanız için generic typelar sağlayabilirsiniz:

interface OrderCreatedData {
  orderId: string;
  customerId: string;
  amount: number;
  currency: string;
}

const orderEvent = new CloudEvent<OrderCreatedData>({
  type: 'com.example.order.created',
  source: '/orders/service',
  data: {
    orderId: '12345',
    customerId: 'cust-789',
    amount: 99.99,
    currency: 'USD'
  }
});

// TypeScript orderEvent.data'nın şeklini biliyor
const amount: number = orderEvent.data!.amount;

Dikkat: CloudEvent objeleri immutable. Bir eventi değiştirmek için cloneWith() methodunu kullan:

const updatedEvent = orderEvent.cloneWith({
  data: {
    ...orderEvent.data!,
    amount: 149.99  // Güncellenmiş miktar
  }
});

Gelen CloudEventleri Parse Etme

AWS Lambda’da eventleri alırken, gelen requestleri parse etmek için HTTP binding kullan:

import { HTTP } from 'cloudevents';
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  try {
    // HTTP requestinden CloudEvent parse et
    const cloudEvent = HTTP.toEvent({
      headers: event.headers,
      body: event.body
    });

    console.log('CloudEvent alındı:', {
      type: cloudEvent.type,
      source: cloudEvent.source,
      id: cloudEvent.id
    });

    // Event datasına eriş
    const payload = cloudEvent.data;

    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Event işlendi' })
    };
  } catch (error) {
    console.error('CloudEvent parse edilemedi:', error);
    return {
      statusCode: 400,
      body: JSON.stringify({ error: 'Geçersiz CloudEvent' })
    };
  }
};

HTTP.toEvent() methodu hem binary hem structured content modlarını destekliyor ve headerlara göre formatı otomatik algılıyor.

CloudEventleri Validate Etme

SDK, CloudEventleri spesifikasyona göre validate ediyor. Gerekli attributelar:

  • type: Event tipi tanımlayıcısı
  • source: Event producer tanımlayıcısı
  • specversion: CloudEvents versiyonu (varsayılan “1.0”)
import { CloudEvent, ValidationError } from 'cloudevents';

function validateAndProcess(eventData: unknown) {
  try {
    // Gerekli attributelar eksikse throw eder
    const event = new CloudEvent(eventData as any);

    // Ek custom validation
    if (!event.type.startsWith('com.example.')) {
      throw new Error('Event type com.example. ile başlamalı.');
    }

    return { valid: true, event };
  } catch (error) {
    if (error instanceof ValidationError) {
      console.error('CloudEvent validation başarısız:', error.message);
      return { valid: false, error: error.message };
    }
    throw error;
  }
}

AWS Lambda Entegrasyon Patternleri

Pattern 1: Lambda Function URL ile CloudEvents

import { CloudEvent, HTTP } from 'cloudevents';
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

interface OrderData {
  orderId: string;
  status: 'pending' | 'completed' | 'failed';
}

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  // Gelen CloudEvent'i parse et
  const cloudEvent = HTTP.toEvent<OrderData>({
    headers: event.headers,
    body: event.body
  });

  // Event datasına type-safe erişim
  const orderData = cloudEvent.data!;

  console.log(`${orderData.orderId} siparişi ${orderData.status} statusu ile işleniyor`);

  // Business logic buraya
  await processOrder(orderData);

  return {
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      message: 'Sipariş işlendi',
      eventId: cloudEvent.id
    })
  };
};

async function processOrder(data: OrderData): Promise<void> {
  // Implementation detayları
}

Pattern 2: EventBridge’e CloudEvent Publish Etme

AWS EventBridge natively CloudEvents formatını desteklemiyor, ancak CloudEventleri detail fieldına embed edebilirsin:

import { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';
import { CloudEvent } from 'cloudevents';

const eventBridge = new EventBridgeClient({});

async function publishToEventBridge(cloudEvent: CloudEvent) {
  // EventBridge belirli bir format bekliyor
  const command = new PutEventsCommand({
    Entries: [
      {
        Source: cloudEvent.source,
        DetailType: cloudEvent.type,
        Detail: JSON.stringify({
          // CloudEvent'i detail fieldına embed et
          cloudevents: {
            specversion: cloudEvent.specversion,
            id: cloudEvent.id,
            time: cloudEvent.time,
            type: cloudEvent.type,
            source: cloudEvent.source,
            datacontenttype: cloudEvent.datacontenttype,
            data: cloudEvent.data
          }
        }),
        EventBusName: 'default'
      }
    ]
  });

  const response = await eventBridge.send(command);

  if (response.FailedEntryCount && response.FailedEntryCount > 0) {
    throw new Error(`Event publish edilemedi: ${JSON.stringify(response.Entries)}`);
  }

  return response;
}

Pattern 3: SNS/SQS ile CloudEvents

SNS ve SQS için CloudEventleri JSON’a serialize et:

import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
import { CloudEvent } from 'cloudevents';

const sns = new SNSClient({});

async function publishToSNS(cloudEvent: CloudEvent, topicArn: string) {
  // CloudEvent'i JSON'a serialize et
  const message = JSON.stringify({
    specversion: cloudEvent.specversion,
    type: cloudEvent.type,
    source: cloudEvent.source,
    id: cloudEvent.id,
    time: cloudEvent.time,
    data: cloudEvent.data
  });

  const command = new PublishCommand({
    TopicArn: topicArn,
    Message: message,
    MessageAttributes: {
      'content-type': {
        DataType: 'String',
        StringValue: 'application/cloudevents+json'
      },
      'ce-type': {
        DataType: 'String',
        StringValue: cloudEvent.type
      },
      'ce-source': {
        DataType: 'String',
        StringValue: cloudEvent.source
      }
    }
  });

  return sns.send(command);
}

Dikkat: SQS’ten consume ederken SNS message wrapper’ını parse etmeyi unutma:

import { SQSEvent } from 'aws-lambda';
import { CloudEvent } from 'cloudevents';

export const handler = async (event: SQSEvent) => {
  for (const record of event.Records) {
    // SQS recordundan SNS message parse et
    const snsMessage = JSON.parse(record.body);
    const cloudEventData = JSON.parse(snsMessage.Message);

    // CloudEvent'i yeniden oluştur
    const cloudEvent = new CloudEvent(cloudEventData);

    console.log('Event işleniyor:', cloudEvent.type);
    // Eventi işle...
  }
};

Type Safety Faydaları

TypeScript’in type sistemi CloudEvents ile iyi çalışıyor:

import { CloudEvent, CloudEventV1, CloudEventV1Attributes } from 'cloudevents';

// Event tiplerini tanımla
type UserCreated = CloudEventV1<{
  userId: string;
  email: string;
  plan: 'free' | 'pro' | 'enterprise';
}>;

type OrderPlaced = CloudEventV1<{
  orderId: string;
  customerId: string;
  items: Array<{ sku: string; quantity: number }>;
}>;

// Type-safe event handler
function handleUserCreated(event: UserCreated) {
  const { userId, email, plan } = event.data!;
  // TypeScript tam şekli biliyor
  if (plan === 'enterprise') {
    // Onboarding flow'u tetikle
  }
}

// Type narrowing ile generic event router
function routeEvent(event: CloudEvent) {
  switch (event.type) {
    case 'com.example.user.created':
      handleUserCreated(event as UserCreated);
      break;
    case 'com.example.order.placed':
      handleOrderPlaced(event as OrderPlaced);
      break;
    default:
      console.warn('Bilinmeyen event tipi:', event.type);
  }
}

Production Mimari Örneği

CloudEvents kullanan pratik bir event-driven mimari:

Storage

Event Consumerlar

Event Processing

Event Producerlar

CloudEvents

CloudEvents

CloudEvents

Validated

Type Filter

Type Filter

Type Filter

API Gateway

Step Functions

External Webhooks

Lambda: Event Validator

EventBridge Bus

Lambda: User Service

Lambda: Notification Service

Lambda: Analytics Service

DynamoDB Event Store

S3 Event Archive

Best Practiceler ve Öğrenilen Dersler

1. Tutarlı Type İsimlendirme Kullan

Event tipleri için reverse-DNS isimlendirme konvansiyonu benimse:

// İyi: Açık hiyerarşi ve ownership
'com.company.service.entity.action'
'com.example.orders.order.created'
'com.example.users.user.updated'

// Kötü: Generic veya belirsiz isimler
'order-created'
'user_event'
'notification'

2. Event Schemalarını Versiyonla

Event tipinde versiyon bilgisi içer:

const event = new CloudEvent({
  type: 'com.example.orders.v1.order.created',  // tipte v1
  source: '/orders/service',
  data: {
    // v1 schema
    orderId: string,
    amount: number
  }
});

// Schema değiştiğinde
const eventV2 = new CloudEvent({
  type: 'com.example.orders.v2.order.created',  // v2 tipi
  source: '/orders/service',
  data: {
    // breaking change'li v2 schema
    orderId: string,
    totalAmount: number,
    currency: string  // yeni required field
  }
});

3. Debug için Eventleri Sakla

CloudEvents, event sourcing ve audit trailleri kolaylaştırıyor:

import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
import { CloudEvent } from 'cloudevents';

const dynamodb = new DynamoDBClient({});

async function storeEvent(event: CloudEvent) {
  const command = new PutItemCommand({
    TableName: 'EventStore',
    Item: {
      eventId: { S: event.id },
      eventType: { S: event.type },
      eventSource: { S: event.source },
      timestamp: { S: event.time || new Date().toISOString() },
      data: { S: JSON.stringify(event.data) },
      // Otomatik cleanup için TTL ekle
      ttl: { N: String(Math.floor(Date.now() / 1000) + 90 * 24 * 60 * 60) }
    }
  });

  await dynamodb.send(command);
}

4. Büyük Payloadları Handle Et

CloudEvents datası büyüyebilir. Dosyalar veya büyük datasetler için claim check patternini kullan:

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({});

async function createEventWithLargePayload(largeData: unknown) {
  // Büyük payloadı S3'e sakla
  const dataKey = `events/${Date.now()}-${Math.random()}.json`;

  await s3.send(new PutObjectCommand({
    Bucket: 'event-payloads',
    Key: dataKey,
    Body: JSON.stringify(largeData),
    ContentType: 'application/json'
  }));

  // Reference ile CloudEvent oluştur
  return new CloudEvent({
    type: 'com.example.data.processed',
    source: '/data/processor',
    data: {
      payloadLocation: `s3://event-payloads/${dataKey}`,
      payloadSize: JSON.stringify(largeData).length
    }
  });
}

5. Extension Attributeları Dikkatli Kullan

CloudEvents custom extension attributeları destekliyor, ama dikkatli kullan:

// Tracing için extension attributelar
const event = new CloudEvent({
  type: 'com.example.order.created',
  source: '/orders/service',
  data: { orderId: '12345' },
  // Custom extensionlar (business logic için önerilmez)
  traceparent: '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01',
  tracestate: 'congo=t61rcWkgMzE'
});

Not: Extensionlar standardize edilmemiş. Domain-specific datayı data fieldına koymayı tercih et.

Gerçek Dünya Etkisi

Multi-service bir mimaride çalışmak, event standardizasyonunun entegrasyon süresini önemli ölçüde azalttığını öğretti. CloudEvents öncesinde yeni bir event consumer eklemek şunları gerektiriyordu:

  1. Dokümantasyon bulmak (genelde güncel değil)
  2. Custom event formatını anlamak
  3. Parsing logic yazmak
  4. Edge caseleri test etmek

CloudEvents ile süreç şöyle oldu:

  1. Event type ve source’u kontrol et
  2. Bilinen yapıya sahip event.dataya eriş
  3. Bitti

Bu, entegrasyon süresini yaklaşık %60 azalttı ve bir parsing bug kategorisini ortadan kaldırdı. Daha da önemlisi, debugging’i kolaylaştırdı - CloudEvents IDleri ve timestampleri, eventleri sistem boyunca trace etmeyi basitleştirdi.

Başlangıç Kontrol Listesi

Serverless projenize CloudEvents ekliyorsanız:

SDK’yı kur: npm install cloudevents Event tiplerini tanımla: Reverse-DNS isimlendirme konvansiyonu kullan TypeScript tipleri oluştur: Event data için interfacelar tanımla Producerları standardize et: Tüm event sourcelerdan CloudEvents emit et Consumerları güncelle: HTTP binding kullanarak CloudEvents parse et Validation ekle: Eventlerin CloudEvents spec’e uygun olduğundan emin ol Eventleri sakla: Debug için event log tut Tipleri dokümante et: Event tipleri ve schemalarının registryını sürdür

Sonuç

CloudEvents, event-driven mimarilerde gerçek bir sorunu çözüyor: standardizasyon eksikliği. TypeScript SDK, CloudEvents’i serverless projelerde önemli bir overhead olmadan benimsemeyi pratik hale getiriyor.

Spesifikasyonun basitliği - sadece birkaç gerekli attribute - incremental olarak kullanmaya başlamayı kolaylaştırıyor. Her şeyi aynı anda migrate etmene gerek yok; yeni eventleri standardize edebilir ve mevcut olanları kademeli olarak güncelleyebilirsin.

Lambda functionları, EventBridge kuralları ve SQS kuyrukları arasında event akışının olduğu serverless mimarilerde, CloudEvents, sistemi oluşturmayı, debug etmeyi ve sürdürmeyi kolaylaştıran ortak dil sağlıyor.

İleri Kaynaklar:

İlgili yazılar

Type-Safe Lambda Middleware: Middy, Zod ve Builder Pattern ile Enterprise Uygulamalar

Middy builder pattern, Zod validation, feature flags ve secrets management kullanarak enterprise serverless uygulamaları için sürdürülebilir, type-safe Lambda middleware nasıl inşa edilir öğren.

aws-lambdamiddymiddleware+8
Middy Yeterli Olmadığında - Özel Lambda Middleware Framework'leri Geliştirme

Bizi Middy'nin sınırlarının ötesine iten production zorluklarını ve performance ile scale için optimize edilmiş özel middleware framework'ümüzü nasıl geliştirdiğimizi keşfedin

aws-lambdamiddlewarecustom-framework+7
AWS Lambda Middleware ile Middy - Temiz Kod ve En İyi Uygulamalar

Middy'nin middleware kalıplarıyla Lambda geliştirmesini nasıl dönüştürdüğünü, tekrarlayan şablonlardan temiz, sürdürülebilir serverless fonksiyonlara geçişi keşfedin

aws-lambdamiddymiddleware+6
TypeScript Geliştiricilerin Monolitten Lambda'ya Taşıdığı Beş Anti-Pattern

DI container'lar, monolitik SDK'lar, god-handler'lar, modül üstü secret çağrıları ve ağır ORM'ler - soğuk başlatmada bedeli ve yerine geçen fonksiyonel yapı.

aws-lambdatypescriptserverless+2
TypeScript AI SDK Karşılaştırması: Agent Geliştirme için Vercel AI SDK vs OpenAI Agents SDK

AI agent geliştirmek için TypeScript SDK'larının pratik karşılaştırması - Vercel AI SDK, OpenAI Agents SDK ve AWS Bedrock entegrasyonu. Kod örnekleri, karar frameworkleri ve production patternleri içeriyor.

typescriptai-toolsserverless+4