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.
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:
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:
- Dokümantasyon bulmak (genelde güncel değil)
- Custom event formatını anlamak
- Parsing logic yazmak
- Edge caseleri test etmek
CloudEvents ile süreç şöyle oldu:
- Event type ve source’u kontrol et
- Bilinen yapıya sahip
event.dataya eriş - 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
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.
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
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
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ı.
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.