JavaScript'te SOLID Prensipleri: TypeScript ve React ile Pratik Rehber
SOLID prensiplerinin modern JavaScript'te uygulanışı: TypeScript, React hooks ve fonksiyonel pattern'lerle pratik örnekler, ayrıca ne zaman gereksiz.
SOLID prensipleri object-oriented programming için formüle edilmişti, ama modern JavaScript geliştirme farklı görünüyor: functional pattern’ler, React hooks, dynamic typing. Prensipler hâlâ geçerli, tek bir adaptasyonla. Her prensibi class hierarchy’ye değil bir composition sınırına (function, modül, hook, component’in prop listesi) bağlayın; beşi de bu geçişten sağ çıkıyor.
Bu beşi Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation ve Dependency Inversion. JavaScript’te değişen, prensiplerin kısıtladığı birim; sağladıkları garanti aynı kalıyor. SOLID’e yüklenen sıkıntının çoğu, Java’ya özgü seremoniyi (factory’ler, derin hierarchy’ler, üst üste binen interface’ler) aynı garantiyi zaten closure’lardan ve structural type’lardan alan bir dile taşımaktan geliyor.
JavaScript Neyi Değiştiriyor#
Prensipler Java ve C# gibi statik tipli, class tabanlı diller için tasarlandı. JavaScript farklı özellikler getiriyor:
- Dynamic typing - TypeScript olmadan compile-time type checking yok
- Functional pattern’ler - Function’lar first-class citizen
- Composition odaklı - Inheritance’a daha az vurgu
- Duck typing - Objeler explicit interface’lerle değil, davranışla validate ediliyor
- React pattern’leri - Hook’lar ve component’ler farklı kısıtlamalara sahip
Single Responsibility Principle (SRP)#
Tanım: Bir modülün değişmek için tek bir nedeni olmalı.
SOLID’in JavaScript’e en doğrudan geçen maddesi bu. İster class, ister function, ister React component yazın; her biri tek bir konuyla ilgilenmeli.
React Component Örneği#
Yaygın ihlal, üç işi tek component’e sıkıştırıyor:
// BAD: SRP'yi ihlal ediyor: Component data fetching, state ve rendering yapıyor
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [userId]);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<img src={user.avatar} alt={user.name} />
</div>
);
}
Bu component’in değişmek için üç nedeni var: data fetching logic, state management veya rendering gereksinimleri. Her concern’i ayır:
// SRP'yi takip ediyor: Data fetching sorumluluğu
function useUser(userId: string) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
return { user, loading, error };
}
// Presentation sorumluluğu
function UserProfile({ userId }: { userId: string }) {
const { user, loading, error } = useUser(userId);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return null;
return <UserCard user={user} />;
}
// Rendering sorumluluğu
function UserCard({ user }: { user: User }) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<img src={user.avatar} alt={user.name} />
</div>
);
}
Şimdi her parçanın tek sorumluluğu var. useUser hook’u bağımsız test edilebilir ve diğer component’lerde yeniden kullanılabilir. UserCard, user data’sının nereden geldiğini bilmeden render ediyor. API’deki değişiklikler sadece useUser’ı etkiliyor, presentation logic’i değil.
Yaygın SRP Anti-pattern’i: God Object’ler#
// BAD: İlgisiz concern'leri olan modül
// utils/user.js
export function validateEmail(email) { /* ... */ }
export function formatCurrency(amount) { /* ... */ }
export function fetchWeatherData(city) { /* ... */ }
export function compressImage(file) { /* ... */ }
Bu function’ların ortak hiçbir yanı yok. Bunları odaklanmış modüllere böl:
// Cohesive modüller
// utils/validation.js
export function validateEmail(email) { /* ... */ }
export function validatePassword(password) { /* ... */ }
// utils/formatting.js
export function formatCurrency(amount) { /* ... */ }
export function formatDate(date) { /* ... */ }
// services/weather.js
export function fetchWeatherData(city) { /* ... */ }
Open/Closed Principle (OCP)#
Tanım: Software entity’leri extension’a açık, modification’a kapalı olmalı.
JavaScript’te bu, mevcut kodu değiştirmeden yeni functionality ekleme demek. Strategy pattern burada iyi çalışıyor.
Payment Processing Örneği#
İhlal, her payment method için yeni bir branch büyüten dispatcher olarak ortaya çıkıyor:
// BAD: OCP'yi ihlal ediyor: Her yeni payment method için function'ı değiştirmeli
function processPayment(amount: number, method: string) {
if (method === 'credit-card') {
validateCreditCard();
chargeCreditCard(amount);
} else if (method === 'paypal') {
validatePayPalAccount();
chargePayPal(amount);
} else if (method === 'crypto') {
validateWallet();
transferCrypto(amount);
}
// Yeni method eklemek bu function'ı değiştirmeyi gerektiriyor
}
Strategy pattern kullanarak functional yaklaşım:
// OCP'yi takip ediyor: Modification olmadan extend ediliyor
type PaymentProcessor = {
validate: () => Promise<boolean>;
charge: (amount: number) => Promise<PaymentResult>;
};
const createCreditCardProcessor = (cardDetails: CardDetails): PaymentProcessor => ({
validate: () => validateCreditCard(cardDetails),
charge: (amount) => chargeCreditCard(cardDetails, amount),
});
const createPayPalProcessor = (email: string): PaymentProcessor => ({
validate: () => validatePayPalAccount(email),
charge: (amount) => chargePayPal(email, amount),
});
const processPayment = async (
processor: PaymentProcessor,
amount: number
): Promise<PaymentResult> => {
const isValid = await processor.validate();
if (!isValid) throw new Error('Payment validation failed');
return processor.charge(amount);
};
// Kullanım - yeni processor eklemek processPayment'ta değişiklik gerektirmiyor
await processPayment(createCreditCardProcessor(cardDetails), 100);
await processPayment(createPayPalProcessor('user@email.com'), 100);
// Mevcut kodu değiştirmeden crypto ekle
const createCryptoProcessor = (wallet: string): PaymentProcessor => ({
validate: () => validateWallet(wallet),
charge: (amount) => transferCrypto(wallet, amount),
});
await processPayment(createCryptoProcessor(walletAddress), 100);
Bu, TypeScript’in structural typing’i sayesinde çalışıyor. Yeni processor’ların tek yapması gereken shape’e uymak; JavaScript explicit interface declaration istemiyor, TypeScript de üstüne compile-time safety ekliyor.
Higher-Order Function’lar ile OCP#
// Composition ile OCP
type Middleware = (data: any) => any;
const compose = (...fns: Middleware[]) => (data: any) =>
fns.reduce((result, fn) => fn(result), data);
// Base transformation'lar
const toLowerCase = (str: string) => str.toLowerCase();
const trim = (str: string) => str.trim();
const removeSpaces = (str: string) => str.replace(/\s/g, '');
// Composing ile extend et - modification gerektirmez
const normalizeEmail = compose(trim, toLowerCase);
const normalizeUsername = compose(trim, toLowerCase, removeSpaces);
// Yeni transformation ekle
const removeDashes = (str: string) => str.replace(/-/g, '');
const normalizePhoneNumber = compose(trim, removeSpaces, removeDashes);
Liskov Substitution Principle (LSP)#
Tanım: Object’ler program’ı bozmadan subtype’ları ile değiştirilebilir olmalı.
LSP ihlalleri inheritance ile yaygın. Klasik örnek:
// BAD: LSP'yi ihlal ediyor: Square, Rectangle'ın contract'ını bozuyor
class Rectangle {
constructor(
protected width: number,
protected height: number
) {}
setWidth(width: number) {
this.width = width;
}
setHeight(height: number) {
this.height = height;
}
getArea(): number {
return this.width * this.height;
}
}
class Square extends Rectangle {
setWidth(width: number) {
this.width = width;
this.height = width; // Beklenmeyen davranış
}
setHeight(height: number) {
this.width = height;
this.height = height; // Beklenmeyen davranış
}
}
// Bu Square ile bozuluyor
function resizeRectangle(rectangle: Rectangle) {
rectangle.setWidth(10);
rectangle.setHeight(5);
console.assert(rectangle.getArea() === 50); // Square ile başarısız (area = 25)
}
Çözüm inheritance yerine composition:
// LSP'yi takip ediyor: Composition kullan
interface Shape {
getArea(): number;
}
class Rectangle implements Shape {
constructor(
private width: number,
private height: number
) {}
setWidth(width: number) {
this.width = width;
}
setHeight(height: number) {
this.height = height;
}
getArea(): number {
return this.width * this.height;
}
}
class Square implements Shape {
constructor(private size: number) {}
setSize(size: number) {
this.size = size;
}
getArea(): number {
return this.size * this.size;
}
}
// Function'lar Shape interface ile çalışıyor - substitution sorunu yok
function printArea(shape: Shape) {
console.log(`Area: ${shape.getArea()}`);
}
printArea(new Rectangle(10, 5)); // Çalışıyor
printArea(new Square(5)); // Çalışıyor
React Component’lerinde LSP#
// BAD: LSP'yi ihlal ediyor: Enhanced button ek prop gerektiriyor
interface ButtonProps {
onClick: () => void;
label: string;
}
function IconButton({ onClick, label, icon }: ButtonProps & { icon: string }) {
if (!icon) {
throw new Error('IconButton requires icon prop'); // BAD: Substitution'ı bozuyor
}
return (
<button onClick={onClick}>
<Icon name={icon} />
{label}
</button>
);
}
// LSP'yi takip ediyor: Optional enhancement
interface IconButtonProps extends ButtonProps {
icon?: string; // Optional - substitution'ı bozmuyor
}
function IconButton({ onClick, label, icon }: IconButtonProps) {
return (
<button onClick={onClick}>
{icon && <Icon name={icon} />}
{label}
</button>
);
}
// Button kullanılan her yerde IconButton kullanılabilir
function Form() {
const handleSubmit = () => console.log('Submitted');
return (
<>
<Button onClick={handleSubmit} label="Submit" />
<IconButton onClick={handleSubmit} label="Submit" icon="check" />
</>
);
}
Interface Segregation Principle (ISP)#
Tanım: Client’lar kullanmadıkları interface’lere depend etmemeli.
JavaScript’in duck typing’i bunu doğal olarak destekliyor, ama TypeScript interface’leri ve React prop’ları explicit segregation’dan faydalanıyor.
Fat Interface Problemi#
// BAD: ISP'yi ihlal ediyor: Fat interface kullanılmayan method implementasyonlarını zorluyor
interface Worker {
work(): void;
eat(): void;
sleep(): void;
getMaintenance(): void;
}
class HumanWorker implements Worker {
work() { console.log('Working...'); }
eat() { console.log('Eating lunch...'); }
sleep() { console.log('Sleeping...'); }
getMaintenance() {
throw new Error('Humans do not need maintenance'); // BAD: Implement etmeye zorlanıyor
}
}
class RobotWorker implements Worker {
work() { console.log('Working...'); }
getMaintenance() { console.log('Getting maintenance...'); }
eat() {
throw new Error('Robots do not eat'); // BAD: Implement etmeye zorlanıyor
}
sleep() {
throw new Error('Robots do not sleep'); // BAD: Implement etmeye zorlanıyor
}
}
Odaklanmış interface’lere böl:
// ISP'yi takip ediyor: Segregated interface'ler
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
interface Maintainable {
getMaintenance(): void;
}
// Sadece gerekli interface'leri implement et
class HumanWorker implements Workable, Eatable, Sleepable {
work() { console.log('Working...'); }
eat() { console.log('Eating lunch...'); }
sleep() { console.log('Sleeping...'); }
}
class RobotWorker implements Workable, Maintainable {
work() { console.log('Working...'); }
getMaintenance() { console.log('Getting maintenance...'); }
}
// Function'lar sadece ihtiyaç duydukları şeye depend ediyor
function makeWork(worker: Workable) {
worker.work();
}
function feedWorker(worker: Eatable) {
worker.eat();
}
const human = new HumanWorker();
const robot = new RobotWorker();
makeWork(human); // Çalışıyor
makeWork(robot); // Çalışıyor
feedWorker(human); // Çalışıyor
// feedWorker(robot); // BAD: Compile error - Robot Eatable implement etmiyor
React Component’lerinde ISP#
// BAD: ISP'yi ihlal ediyor: Component tüm User type'a depend ediyor
interface User {
id: string;
name: string;
email: string;
address: Address;
phoneNumber: string;
preferences: UserPreferences;
billingInfo: BillingInfo;
// ... 20+ property daha
}
function UserGreeting({ user }: { user: User }) {
return <h1>Hello, {user.name}!</h1>; // Sadece name kullanıyor
}
// ISP'yi takip ediyor: Component sadece kullandığına depend ediyor
interface UserGreetingProps {
name: string;
}
function UserGreeting({ name }: UserGreetingProps) {
return <h1>Hello, {name}!</h1>;
}
// Kullanım: Sadece gerekli data'yı geç
function App() {
const user: User = fetchUser();
return <UserGreeting name={user.name} />;
}
ISP için TypeScript Utility Type’ları#
// Büyük interface
interface User {
id: string;
email: string;
password: string;
firstName: string;
lastName: string;
address: Address;
phoneNumber: string;
createdAt: Date;
}
// Utility type'lar kullanarak focused type'lar oluştur
type UserCredentials = Pick<User, 'email' | 'password'>;
type UserProfile = Pick<User, 'id' | 'firstName' | 'lastName' | 'email'>;
type UserContactInfo = Pick<User, 'email' | 'phoneNumber' | 'address'>;
type PublicUser = Omit<User, 'password'>;
// Component'ler sadece gerekli property'leri alıyor
function UserContactForm({ email, phoneNumber, address }: UserContactInfo) {
// Component logic
}
Dependency Inversion Principle (DIP)#
Tanım: High-level modüller low-level modüllere depend etmemeli. İkisi de abstraction’lara depend etmeli.
Bu testability ve flexibility için kritik.
Problem: Direct Dependency’ler#
// BAD: DIP'yi ihlal ediyor: UserService doğrudan concrete implementation'lara depend ediyor
class MySQLDatabase {
connect() { /* ... */ }
query(sql: string) { /* ... */ }
}
class EmailService {
sendEmail(to: string, subject: string, body: string) {
// Doğrudan Gmail SMTP kullanıyor
}
}
class UserService {
private db = new MySQLDatabase(); // BAD: Tight coupling
private emailService = new EmailService(); // BAD: Tight coupling
async createUser(userData: UserData) {
await this.db.connect();
const user = await this.db.query('INSERT INTO users...');
await this.emailService.sendEmail(user.email, 'Welcome', 'Welcome!');
return user;
}
}
// Gerçek MySQL ve email service olmadan test edilemiyor
// Implementation'ları değiştirilemiyor
Abstraction’lar ile constructor injection kullan:
// DIP'yi takip ediyor: Abstraction'lara depend et
interface Database {
connect(): Promise<void>;
query<T>(sql: string, params?: any[]): Promise<T>;
}
interface EmailProvider {
send(to: string, subject: string, body: string): Promise<void>;
}
// Concrete implementation'lar
class MySQLDatabase implements Database {
async connect() { /* MySQL connection */ }
async query<T>(sql: string, params?: any[]): Promise<T> {
/* MySQL query */
return {} as T;
}
}
class PostgreSQLDatabase implements Database {
async connect() { /* PostgreSQL connection */ }
async query<T>(sql: string, params?: any[]): Promise<T> {
/* PostgreSQL query */
return {} as T;
}
}
// High-level modül abstraction'lara depend ediyor
class UserService {
constructor(
private db: Database,
private emailProvider: EmailProvider
) {}
async createUser(userData: UserData) {
await this.db.connect();
const user = await this.db.query<User>('INSERT INTO users...', [userData]);
await this.emailProvider.send(user.email, 'Welcome', 'Welcome!');
return user;
}
}
// Production: Gerçek implementation'ları inject et
const userService = new UserService(
new MySQLDatabase(),
new GmailEmailProvider()
);
// Testing: Mock'ları inject et
const testUserService = new UserService(
new MockDatabase(),
new MockEmailProvider()
);
// Implementation'ları kolayca değiştir
const productionUserService = new UserService(
new PostgreSQLDatabase(),
new SendGridEmailProvider()
);
React’te Context API ile DIP#
// React Context ile dependency injection
interface ApiClient {
get<T>(url: string): Promise<T>;
post<T>(url: string, data: any): Promise<T>;
}
interface Logger {
log(message: string): void;
error(message: string, error: Error): void;
}
// Context'leri oluştur
const ApiClientContext = React.createContext<ApiClient | null>(null);
const LoggerContext = React.createContext<Logger | null>(null);
// Dependency'ler için custom hook'lar
function useApiClient() {
const client = useContext(ApiClientContext);
if (!client) throw new Error('ApiClient not provided');
return client;
}
function useLogger() {
const logger = useContext(LoggerContext);
if (!logger) throw new Error('Logger not provided');
return logger;
}
// Component abstraction'lara depend ediyor
function UserList() {
const apiClient = useApiClient();
const logger = useLogger();
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
apiClient.get<User[]>('/users')
.then(setUsers)
.catch(error => logger.error('Failed to fetch users', error));
}, [apiClient, logger]);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
// Root'ta dependency'leri provide et
function App() {
const apiClient = useMemo(() => new FetchApiClient(), []);
const logger = useMemo(() => new ConsoleLogger(), []);
return (
<ApiClientContext.Provider value={apiClient}>
<LoggerContext.Provider value={logger}>
<UserList />
</LoggerContext.Provider>
</ApiClientContext.Provider>
);
}
// Testing: Mock'ları provide et
function TestApp() {
const mockApiClient = useMemo(() => new MockApiClient(), []);
const mockLogger = useMemo(() => new MockLogger(), []);
return (
<ApiClientContext.Provider value={mockApiClient}>
<LoggerContext.Provider value={mockLogger}>
<UserList />
</LoggerContext.Provider>
</ApiClientContext.Provider>
);
}
Aşırı Mühendislik Tuzakları#
Tüm kod strict SOLID’den faydalanmıyor. Premature abstraction gereksiz complexity yaratıyor.
Overkill Örneği: Basit String Formatting#
// BAD: Basit utility için overkill
interface StringFormatter {
format(input: string): string;
}
class UpperCaseFormatter implements StringFormatter {
format(input: string): string {
return input.toUpperCase();
}
}
class FormatterFactory {
static create(type: string): StringFormatter {
switch (type) {
case 'uppercase':
return new UpperCaseFormatter();
default:
throw new Error('Unknown formatter');
}
}
}
// Basit function yeterli
const toUpperCase = (str: string) => str.toUpperCase();
Sıkı Uygulamanın Değdiği Yerler#
- Birden fazla takımın çalıştığı büyük codebase’ler
- Public API’ı olan kütüphaneler ve framework’ler
- Uzun ömürlü enterprise uygulamalar
- Yüksek testability isteyen karmaşık business logic
- Implementation’ı değiştirebilmesi gereken sistemler
Gevşetmenin Doğru Olduğu Yerler#
- Hızın architecture’dan önce geldiği prototype’lar ve MVP’ler
- Abstraction maliyetinin faydasını aştığı küçük utility function’lar
- Requirement değişikliği beklenmeyen stabil CRUD uygulamaları
- Kısa ömürlü one-off script’ler ve tool’lar
- Pattern’ler henüz belirmediğinde: abstract etmeden önce duplication’ı bekleyin
TypeScript’in Rolü#
TypeScript bu kontrollerin bir kısmını compile time’a taşıyor; yardımcı olamadığı sınırı da açıkça gösteriyor:
// ISP ve DIP için explicit interface'ler
interface PaymentProcessor {
process(amount: number): Promise<void>;
}
class CreditCardProcessor implements PaymentProcessor {
async process(amount: number): Promise<void> {
// Implement etmeli yoksa compile error
}
}
// Type checking bu LSP ihlalini yakalamıyor: signature uyuyor, davranış uymuyor
interface Bird {
fly(): void;
}
class Penguin implements Bird {
fly() {
throw new Error('Cannot fly'); // Sorunsuz derleniyor, runtime'da patlıyor
}
}
// Daha iyi: interface'leri ayır ki derleyici hatalı çağrıyı eleyebilsin
interface FlyingBird {
fly(): void;
}
interface SwimmingBird {
swim(): void;
}
class Sparrow implements FlyingBird {
fly() { console.log('Flying'); }
}
class SwimmingPenguin implements SwimmingBird {
swim() { console.log('Swimming'); }
}
// Type-safe abstraction için generic'ler (DIP ve LSP)
interface Repository<T> {
findById(id: string): Promise<T | null>;
save(entity: T): Promise<T>;
delete(id: string): Promise<void>;
}
class UserRepository implements Repository<User> {
async findById(id: string): Promise<User | null> {
return null;
}
async save(user: User): Promise<User> {
return user;
}
async delete(id: string): Promise<void> {}
}
// Class hierarchy'lere alternatif union type'lar
type Shape =
| { type: 'circle'; radius: number }
| { type: 'rectangle'; width: number; height: number }
| { type: 'triangle'; base: number; height: number };
function calculateArea(shape: Shape): number {
switch (shape.type) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return (shape.base * shape.height) / 2;
}
}
Sınırı Nereye Çekmeli#
Prensip, kodun zaten sahip olduğu bir sınıra oturduğunda varsayılan geçerli. Tek bir concern’i üstlenen custom hook, dispatcher’a dokunmadan eklenen strategy objesi, component’in gerçekten okuduğu alanlara daraltılmış prop listesi, database’ini kendisi kurmak yerine dışarıdan alan constructor: SOLID’in bir TypeScript codebase’ine taşıdığı değerin çoğu bu dört hamlede ve hiçbiri class hierarchy istemiyor.
Abstraction’ın gerekçesi tükendiğinde varsayılandan sapın. Tek implementation’ı olan interface, tek branch’i olan factory, argümanlarını yalnızca ileten hook; hepsi henüz kimsenin ihtiyaç duymadığı esneklik için kurulum maliyeti ödüyor. Abstract etmeden önce ikinci implementation’ı bekleyin. Prototipte veya bitiş tarihi belli bir script’te katmanı atlamak doğru karar; okunacak kod azaldığı için sonuç da okunur kalıyor.
Kaynaklar#
- Clean Code Bölüm 8: SOLID Prensipleri - Robert C. Martin (yeni sekmede açılır) - Uncle Bob’un nesne yönelimli tasarımda SOLID üzerine temel dersleri
- Refactoring.Guru: Tasarım Kalıpları (yeni sekmede açılır) - TypeScript örnekleriyle SOLID prensiplerini tamamlayan kalıpların pratik kataloğu
- MDN: JavaScript Sınıfları (yeni sekmede açılır) - JavaScript’te OOP ve SOLID’in altında yatan sınıf sözdizimi referansı
- typescript-eslint: Başlangıç (yeni sekmede açılır) - Derleme zamanında SOLID uyumlu TypeScript kurallarını uygulamak için araçlar
- MDN: Sınıfları Kullanma - JavaScript Rehberi (yeni sekmede açılır) - JavaScript sınıf mekanizmaları ve prototip kalıtımına ayrıntılı rehber
İlgili yazılar
Decorator, Adapter, Facade, Composite ve Proxy patternlerinin React ve TypeScript'te evrimi: HOC'lar ne zaman hook'lara yol verir, adapterler API'ları nasıl izole eder.
typescript · react · design-patterns +2
Singleton, Factory, Builder ve Prototype pattern'lerinin TypeScript'te evrimi: ES modülleri singleton'ı ne zaman, factory function'lar class'ı ne zaman geçer.
typescript · design-patterns · architecture +1
Domain-Driven Design'a kapsamlı giriş: temel kavramlar, yapı taşları, stratejik desenler ve DDD'yi ne zaman ve nasıl uygulayacağına dair rehber.
domain-driven-design · architecture · design-patterns +2
CDK stack düzeni için yaşam döngüsü testi: bir kaynağın ömrü tek bir dağıtımdan uzunsa kendi uzun ömürlü stack'ine koyun, ona bilinen bir adla erişin.
aws-cdk · infrastructure-as-code · typescript +3
Mimari ağırlığını runtime'ın init-amortismanına göre seç: single-purpose Lambda'da yalın handler, Lambdalith'te orta, tam OOP/DI yalnızca uzun ömürlü runtime'da.
architecture · lambda · serverless +3