TypeScript Decorators: Metadata ve Aspect-Oriented Programming

TypeScript Decorators: Metadata ve Aspect-Oriented Programming
Decorator'lar, sınıflara ve üyelerine metadata eklemek veya davranışlarını değiştirmek için kullanılan güçlü bir TypeScript özelliğidir. Angular, NestJS ve TypeORM gibi popüler framework'lerin temelini oluşturur. Bu yazıda decorator'ların kullanımını, türlerini ve pratik uygulamalarını inceleyeceğiz.
Decorator Nedir?
Decorator, bir sınıf, method, property veya parametreye @ sembolü ile eklenen özel bir fonksiyondur. Derleme zamanında çalışır ve hedefin davranışını değiştirebilir:
// tsconfig.json'da etkinleştirme
// "experimentalDecorators": true
// "emitDecoratorMetadata": true
// Basit class decorator
function Sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@Sealed
class KullaniciServisi {
kullaniciGetir(id: number): string {
return `Kullanıcı ${id}`;
}
}
// Decorator factory (parametre alan decorator)
function Logger(prefix: string) {
return function (constructor: Function) {
console.log(`${prefix}: ${constructor.name} sınıfı oluşturuldu`);
};
}
@Logger("API")
class ApiServisi {
// ...
}
Method Decorator
// Loglama decorator'ı
function Log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`${propertyKey} çağrıldı, parametreler:`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} sonuç:`, result);
return result;
};
return descriptor;
}
// Performans ölçüm decorator'ı
function MeasureTime(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const start = performance.now();
const result = await originalMethod.apply(this, args);
const end = performance.now();
console.log(`${propertyKey}: ${(end - start).toFixed(2)}ms`);
return result;
};
return descriptor;
}
class KullaniciServisi {
@Log
@MeasureTime
async kullaniciGetir(id: number): Promise<any> {
const response = await fetch(`/api/kullanicilar/${id}`);
return response.json();
}
}
@Log @MeasureTime sırasında önce MeasureTime, sonra Log çalışır.
Property Decorator
// Doğrulama decorator'ı
function MinLength(min: number) {
return function (target: any, propertyKey: string) {
let value: string;
const getter = () => value;
const setter = (newVal: string) => {
if (newVal.length < min) {
throw new Error(
`${propertyKey} en az ${min} karakter olmalıdır`
);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
function MaxLength(max: number) {
return function (target: any, propertyKey: string) {
let value: string;
const getter = () => value;
const setter = (newVal: string) => {
if (newVal.length > max) {
throw new Error(
`${propertyKey} en fazla ${max} karakter olmalıdır`
);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class KullaniciFormu {
@MinLength(3)
@MaxLength(50)
isim: string = "";
@MinLength(5)
email: string = "";
}
TC39 Stage 3 Decorator'lar (TypeScript 5.0+)
TypeScript 5.0 ile birlikte TC39 standart decorator'ları desteklenmektedir:
// Yeni standart decorator syntax
function logged(
originalMethod: any,
context: ClassMethodDecoratorContext
) {
const methodName = String(context.name);
function replacementMethod(this: any, ...args: any[]) {
console.log(`LOG: ${methodName} çağrıldı`);
const result = originalMethod.call(this, ...args);
console.log(`LOG: ${methodName} tamamlandı`);
return result;
}
return replacementMethod;
}
// Class decorator (yeni syntax)
function sealed(
constructor: Function,
context: ClassDecoratorContext
) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
// Auto-accessor decorator
function observable(
accessor: { get: () => any; set: (value: any) => void },
context: ClassAccessorDecoratorContext
) {
return {
get(this: any) {
return accessor.get.call(this);
},
set(this: any, value: any) {
console.log(`${String(context.name)} değişti: ${value}`);
accessor.set.call(this, value);
}
};
}
class Kullanici {
@logged
selamla(isim: string): string {
return `Merhaba, ${isim}!`;
}
}
experimentalDecorators) birlikte kullanılamaz. Yeni projelerde standart decorator'ları tercih edin. Angular ve NestJS gibi framework'ler henüz experimental decorator'lar kullanmaktadır; framework'ünüzün gereksinimlerini kontrol edin.
Pratik Decorator Pattern'leri
// Retry decorator
function Retry(maxRetries: number = 3) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
let lastError: Error;
for (let i = 0; i <= maxRetries; i++) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
lastError = error as Error;
console.log(`Deneme ${i + 1}/${maxRetries + 1} başarısız`);
if (i < maxRetries) {
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}
throw lastError!;
};
return descriptor;
};
}
// Cache decorator
function Cache(ttlMs: number = 60000) {
const cache = new Map<string, { value: any; expiry: number }>();
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && cached.expiry > Date.now()) {
return cached.value;
}
const result = await originalMethod.apply(this, args);
cache.set(key, { value: result, expiry: Date.now() + ttlMs });
return result;
};
return descriptor;
};
}
class VeriServisi {
@Retry(3)
@Cache(30000) // 30 saniye cache
async kullanicilariGetir(): Promise<any[]> {
const response = await fetch("/api/kullanicilar");
return response.json();
}
}
Sonuç
Decorator'lar, TypeScript'te metaprogramlama ve kod organizasyonu için güçlü bir araçtır. Class, method, property ve parameter decorator'lar ile farklı senaryolara çözüm üretebilirsiniz. TypeScript 5.0+ ile gelen standart decorator'lar, gelecekte framework'lerin de geçiş yapacağı standarttır. Decorator'ları akıllıca kullanarak daha temiz, okunabilir ve bakımı kolay kod yazabilirsiniz.