Skip to content
~/sph.sh

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 mimarilerle çalışırken tekrar eden bir sorunla karşılaştım: her event kaynağı eventleri farklı şekilde tanımlıyor. Bir Lambda { userId: string } beklerken, diğeri { user_id: string } kullanıyor, üçüncüsü ise { sub: string } tercih ediyor. Bu tutarsızlık entegrasyon kabusları yaratıyor ve yeniden kullanılabilir event processorları oluşturmayı zorlaştırıyor.

CloudEvents bu sorunu, event verilerini standart bir şekilde tanımlamak için CNCF spesifikasyonu sağlayarak çözüyor. İşte CloudEvents TypeScript SDK'sını serverless projelerde kullanırken öğrendiklerim.

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:

typescript
{  "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.

CloudEvents SDK Kurulumu

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

bash
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:

typescript
import { CloudEvent } from 'cloudevents';
// Basit event oluşturmaconst 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 UUIDconsole.log(event.time); // Otomatik generate edilen timestamp

Event Data için Type Safety

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

typescript
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 biliyorconst amount: number = orderEvent.data!.amount;

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

typescript
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:

typescript
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")
typescript
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

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
import { CloudEvent, CloudEventV1, CloudEventV1Attributes } from 'cloudevents';
// Event tiplerini tanımlatype 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 handlerfunction 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 routerfunction 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:

Best Practiceler ve Öğrenilen Dersler

1. Tutarlı Type İsimlendirme Kullan

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

typescript
// İ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:

typescript
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ğindeconst 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:

typescript
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:

typescript
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:

typescript
// Tracing için extension attributelarconst 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