Skip to content

Yazilim Mimarisi & Best Practices -- Kapsamli Rehber

📌 Bu Rehber Nedir?

Tüm backend rehberlerinin ustundeki "nasil duzgun yazilir" katmani. Framework'den bagimsiz yazilim muhendisligi prensipleri.

Kapsam: Mimari yaklasimlar, design patterns, SOLID, clean code, API tasarimi, auth, caching, queue, error handling, CI/CD, güvenlik, performans, testing


1) Mimari Yaklasimlar

Yazilim mimarisi, bir sistemin yüksek seviyeli yapisini tanimlar. Doğru mimari secimi projenin basarisini, olceklenebilirligini ve bakim kolayligini dogrudan etkiler.

1.1 Monolith (Yekpare Mimari)

Tüm uygulama tek bir codebase ve tek bir deployment unit olarak çalışır. Tüm moduller (auth, payment, notification, reporting) ayni process icinde yer alir.

Tipik Monolith Proje Yapisi:

my-app/
  src/
    controllers/
      AuthController.ts
      ProductController.ts
      OrderController.ts
    services/
      AuthService.ts
      ProductService.ts
      OrderService.ts
    repositories/
      UserRepository.ts
      ProductRepository.ts
      OrderRepository.ts
    models/
      User.ts
      Product.ts
      Order.ts
    middlewares/
      authMiddleware.ts
      rateLimiter.ts
    routes/
      index.ts
    utils/
      logger.ts
      validator.ts
    app.ts
  tests/
  package.json
  tsconfig.json

Avantaj / Dezavantaj Tablosu:

AvantajDezavantaj
Basit deployment (tek artifact)Buyudukce karmasiklasir
Kolay debugging (tek process)Tek bir hata tüm sistemi etkiler
Düşük operasyonel maliyetlerBagimsiz olcekleme mumkun degil
Kolay transaction yönetimiBüyük takimlarda merge conflict'ler artar
IDE'de tüm kodu gorursunBuild süresi uzar (büyük projelerde)
Hızlı prototiplemeTeknoloji degistirmek cok zor

Ne Zaman Monolith?

  • Küçük-orta ölçekli projeler (0-5 gelistirici)
  • MVP / proof of concept
  • Domain henuz net degil (sinirlari kesfediyorsun)
  • Startup'in ilk versiyonu
typescript
// Monolith Express.js ornegi -- her sey tek app icinde
import express from 'express';
import { authRoutes } from './routes/auth';
import { productRoutes } from './routes/product';
import { orderRoutes } from './routes/order';
import { notificationRoutes } from './routes/notification';

const app = express();
app.use(express.json());

// Tum moduller tek uygulama icinde
app.use('/api/auth', authRoutes);
app.use('/api/products', productRoutes);
app.use('/api/orders', orderRoutes);
app.use('/api/notifications', notificationRoutes);

app.listen(3000, () => console.log('Monolith running on :3000'));

1.2 Microservices (Mikro Servis Mimarisi)

Her bir is birimini (domain) bagimsiz bir servis olarak ayirirsin. Her servis kendi veritabanina sahip olabilir, bagimsiz deploy edilir ve farkli dillerde yazılabilir.

Ne Zaman Microservices'e Gec?

Asagidaki isaretlerden en az 3-4 tanesi varsa:

  • Takimin 15+ gelistirici ve birbirinin kodunu bozuyor
  • Farkli moduller farkli olceklenme ihtiyaci duyuyor (ornegin: siparis servisi cok yogun, raporlama düşük)
  • Deployment bir modul için tüm sistemi riske atiyor
  • Farkli teknoloji ihtiyaclari var (bir modul Python ML, digeri Node.js)
  • Monolit build/test süresi 30+ dakikayi gecti
  • Domain sinirlar netlesti, bounded context'ler ortaya çıktı

Service Decomposition Stratejileri:

StratejiAçıklamaÖrnek
Business CapabilityIs yeteneklerine gore bolPayment Service, Inventory Service
Subdomain (DDD)Domain-Driven Design bounded contextOrder BC, Shipping BC
Verb/UsecaseIs aksiyonlarina goreCheckoutService, SearchService
Data OwnershipVeriye sahiplige goreCustomerDataService, ProductCatalogService

Bounded Context Ornegi (E-ticaret):

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  Product Catalog │    │  Order Service   │    │ Payment Service  │
│  Service         │    │                  │    │                  │
│  ─────────────── │    │  ─────────────── │    │  ─────────────── │
│  - product CRUD  │    │  - order create  │    │  - charge        │
│  - categories    │◄──►│  - order status  │◄──►│  - refund        │
│  - search/filter │    │  - cart          │    │  - invoice       │
│  ─────────────── │    │  ─────────────── │    │  ─────────────── │
│  PostgreSQL      │    │  PostgreSQL      │    │  PostgreSQL      │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                      │                      │
         └──────────────────────┼──────────────────────┘

                    ┌───────────┴───────────┐
                    │    API Gateway         │
                    │    (Kong / Nginx)      │
                    └───────────────────────┘
typescript
// Order Service -- bagimsiz Express app
// services/order-service/src/app.ts
import express from 'express';
import { orderRoutes } from './routes/order';
import { connectDB } from './database';

const app = express();
app.use(express.json());
app.use('/api/orders', orderRoutes);

connectDB().then(() => {
  app.listen(3001, () => console.log('Order Service running on :3001'));
});

// Diger servisle iletisim: HTTP veya Message Queue
import axios from 'axios';

async function createOrder(orderData: CreateOrderDTO) {
  // 1. Order'i kaydet
  const order = await orderRepository.create(orderData);

  // 2. Payment Service'e HTTP istegi
  const payment = await axios.post('http://payment-service:3002/api/payments', {
    orderId: order.id,
    amount: order.totalAmount,
    currency: 'TRY'
  });

  // 3. Notification Service'e event gonder (async -- queue uzerinden)
  await messageQueue.publish('order.created', {
    orderId: order.id,
    userId: order.userId,
    email: orderData.email
  });

  return order;
}

1.3 Serverless (FaaS -- Function as a Service)

Her bir endpoint veya işlem, bagimsiz bir fonksiyon olarak çalışır. Altyapi yönetimi cloud provider'a birakilir. Sadece fonksiyon calisirken ucret odersin.

Yaygin Platformlar:

  • AWS Lambda + API Gateway
  • Vercel Functions (Next.js ile)
  • Cloudflare Workers
  • Google Cloud Functions
  • Azure Functions
typescript
// AWS Lambda ornegi -- siparis olusturma
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDB } from 'aws-sdk';

const dynamodb = new DynamoDB.DocumentClient();

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  try {
    const body = JSON.parse(event.body || '{}');

    const order = {
      id: generateId(),
      userId: body.userId,
      items: body.items,
      total: calculateTotal(body.items),
      status: 'pending',
      createdAt: new Date().toISOString()
    };

    await dynamodb.put({
      TableName: 'Orders',
      Item: order
    }).promise();

    return {
      statusCode: 201,
      body: JSON.stringify({ success: true, data: order })
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ success: false, message: 'Internal server error' })
    };
  }
};
typescript
// Vercel Serverless Function ornegi
// api/products/[id].ts
import type { VercelRequest, VercelResponse } from '@vercel/node';

export default async function handler(req: VercelRequest, res: VercelResponse) {
  const { id } = req.query;

  if (req.method === 'GET') {
    const product = await db.product.findUnique({ where: { id: String(id) } });
    if (!product) return res.status(404).json({ message: 'Product not found' });
    return res.status(200).json({ success: true, data: product });
  }

  return res.status(405).json({ message: 'Method not allowed' });
}

Serverless Avantaj / Dezavantaj:

AvantajDezavantaj
Sifir altyapi yönetimiCold start sorunu (ilk istek yavas)
Otomatik olceklemeVendor lock-in riski
Kullanildigi kadar odeDebugging ve monitoring zor
Hızlı deployExecution time limiti (15 dk AWS Lambda)
Yüksek elde edilebilirlikStateful islemler için uygun degil

1.4 Modular Monolith (Moduler Yekpare)

Monolith'in basitligini korurken, iceride domain-driven modullere ayirirsin. Her modul kendi controller, service, repository katmanina sahiptir. Moduller arasi iletisim tanimli interface'ler uzerinden olur.

my-app/
  src/
    modules/
      product/
        controllers/ProductController.ts
        services/ProductService.ts
        repositories/ProductRepository.ts
        models/Product.ts
        dto/CreateProductDTO.ts
        product.module.ts          # modul tanimi
        product.routes.ts
      order/
        controllers/OrderController.ts
        services/OrderService.ts
        repositories/OrderRepository.ts
        models/Order.ts
        dto/CreateOrderDTO.ts
        order.module.ts
        order.routes.ts
      payment/
        controllers/PaymentController.ts
        services/PaymentService.ts
        ...
      notification/
        services/NotificationService.ts
        ...
    shared/
      middleware/
      utils/
      database/
    app.ts
typescript
// modules/product/product.module.ts
// Her modulun disindan erisim sadece tanimli interface uzerinden
export interface IProductModule {
  getProductById(id: string): Promise<ProductDTO>;
  getProductsByIds(ids: string[]): Promise<ProductDTO[]>;
  checkStock(productId: string, quantity: number): Promise<boolean>;
}

export class ProductModule implements IProductModule {
  constructor(private productService: ProductService) {}

  async getProductById(id: string): Promise<ProductDTO> {
    return this.productService.findById(id);
  }

  async getProductsByIds(ids: string[]): Promise<ProductDTO[]> {
    return this.productService.findByIds(ids);
  }

  async checkStock(productId: string, quantity: number): Promise<boolean> {
    return this.productService.hasStock(productId, quantity);
  }
}

// modules/order/services/OrderService.ts
// Order modulu, Product modulune interface uzerinden erisir
class OrderService {
  constructor(
    private orderRepo: OrderRepository,
    private productModule: IProductModule // modul arayuzu
  ) {}

  async createOrder(dto: CreateOrderDTO) {
    // Product modulune interface uzerinden eris
    for (const item of dto.items) {
      const hasStock = await this.productModule.checkStock(item.productId, item.quantity);
      if (!hasStock) {
        throw new InsufficientStockError(item.productId);
      }
    }

    return this.orderRepo.create(dto);
  }
}

Modular Monolith Ne Zaman?

  • Domain sinirlar belli ama microservices operasyonel yukune hazir degilsin
  • Takimda 5-15 gelistirici
  • Ileride microservices'e gecis ihtimali var (moduller zaten hazir)
  • Monolith'in "spagetti" olmasini onlemek istiyorsun

1.5 Karar Agaci -- Hangi Mimariy Secmeliyim?

KriterMonolithModular MonolithMicroservicesServerless
Takimda kisi1-55-1515+1-10
Proje yasam süresi0-2 yil2-5 yil5+ yilDeğişken
Domain karmasikligiDüşükOrta-YüksekYüksekDüşük-Orta
Olceklenme ihtiyaciDüşükOrtaYüksekOtomatik
DevOps olgunluguDüşükOrtaYüksek (zorunlu)Düşük
MVP / prototipEn uygunUygunAsiri muhendislikUygun
Bagimsiz deployYokYokVarVar (fonksiyon bazli)
Teknoloji cesitliligiTek dilTek dilHer servis farkli olabilirGenellikle tek dil
Operasyonel maliyetDüşükDüşük-OrtaYüksekDeğişken

Önemli Kural

"Microservices" bir hedef degil, bir aractir. Cogu proje Monolith veya Modular Monolith ile baslamali. Erken mikroservis gecisi gereksiz karmasiklik ve operasyonel yuk getirir. Martin Fowler: "Don't start with microservices -- monolith first."


2) Katmanli Mimari (Layered Architecture)

Katmanli mimari, uygulama kodunu sorumluluk alanlarina gore yatay katmanlara ayirir. Her katman sadece bir alt katmanla iletisim kurar.

2.1 Katmanlar ve Akis

HTTP Request


┌─────────────────────────────┐
│  Controller (Presentation)  │  ← HTTP request/response isler
│  - Route handler            │  ← Input validation yapar
│  - Request parsing          │  ← Response format belirler
└──────────────┬──────────────┘
               │ DTO

┌─────────────────────────────┐
│  Service (Business Logic)   │  ← Is kurallari burada
│  - Validation               │  ← Transaction yonetimi
│  - Business rules           │  ← Diger servisleri cagirabilir
│  - Orchestration            │
└──────────────┬──────────────┘
               │ Entity / Model

┌─────────────────────────────┐
│  Repository (Data Access)   │  ← Veritabani islemleri
│  - CRUD operations          │  ← Query builder / ORM
│  - Query optimization       │  ← Cache layer
└──────────────┬──────────────┘
               │ SQL / Query

┌─────────────────────────────┐
│  Database (Persistence)     │  ← PostgreSQL, MongoDB, vs.
└─────────────────────────────┘

2.2 Her Katmanin Sorumlulugu

KatmanNe YapmaliNe Yapmamali
ControllerHTTP request parse et, input validate et, response dondurIs mantigi yazma, direkt DB sorgusu yapma
ServiceIs kurallarini uygula, transaction yonet, servisleri koordine etHTTP request/response ile ilgilenme, SQL yazma
RepositoryDB CRUD islemleri, query optimize etIs kurali uygulama, HTTP bilgisi kullanma
Model/EntityVeri yapisini tanimla, basit validasyonlarIs mantigi barindirma (anemic model tartismali)
DTOKatmanlar arasi veri tasima, response seklini belirleIs mantigi barindirma
MiddlewareCross-cutting concerns (auth, logging, rate limit)Is mantigi, DB erisimi

2.3 Gercek Dunya Ornegi: E-ticaret Product CRUD

Bir e-ticaret uygulamasinda urun yönetimi -- tüm katmanlariyla:

Model:

typescript
// models/Product.ts
export interface Product {
  id: string;
  name: string;
  slug: string;
  description: string;
  price: number;
  stock: number;
  categoryId: string;
  images: string[];
  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
}

DTO'lar:

typescript
// dto/CreateProductDTO.ts
export interface CreateProductDTO {
  name: string;
  description: string;
  price: number;
  stock: number;
  categoryId: string;
  images?: string[];
}

// dto/UpdateProductDTO.ts
export interface UpdateProductDTO {
  name?: string;
  description?: string;
  price?: number;
  stock?: number;
  categoryId?: string;
  images?: string[];
  isActive?: boolean;
}

// dto/ProductResponseDTO.ts
export interface ProductResponseDTO {
  id: string;
  name: string;
  slug: string;
  description: string;
  price: number;
  formattedPrice: string;  // "149,99 TL"
  stock: number;
  inStock: boolean;
  category: {
    id: string;
    name: string;
  };
  images: string[];
  createdAt: string;
}

// dto/ProductListDTO.ts
export interface ProductListDTO {
  items: ProductResponseDTO[];
  meta: {
    total: number;
    page: number;
    limit: number;
    totalPages: number;
  };
}

Repository:

typescript
// repositories/ProductRepository.ts
import { Pool } from 'pg';
import { Product } from '../models/Product';

export class ProductRepository {
  constructor(private db: Pool) {}

  async findById(id: string): Promise<Product | null> {
    const result = await this.db.query(
      'SELECT * FROM products WHERE id = $1',
      [id]
    );
    return result.rows[0] || null;
  }

  async findAll(options: {
    page: number;
    limit: number;
    categoryId?: string;
    search?: string;
    sortBy?: string;
    sortOrder?: 'asc' | 'desc';
  }): Promise<{ items: Product[]; total: number }> {
    const { page, limit, categoryId, search, sortBy = 'created_at', sortOrder = 'desc' } = options;
    const offset = (page - 1) * limit;
    const conditions: string[] = ['is_active = true'];
    const params: any[] = [];

    if (categoryId) {
      params.push(categoryId);
      conditions.push(`category_id = $${params.length}`);
    }

    if (search) {
      params.push(`%${search}%`);
      conditions.push(`(name ILIKE $${params.length} OR description ILIKE $${params.length})`);
    }

    const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';

    const countResult = await this.db.query(
      `SELECT COUNT(*) FROM products ${where}`,
      params
    );

    params.push(limit, offset);
    const result = await this.db.query(
      `SELECT * FROM products ${where} ORDER BY ${sortBy} ${sortOrder} LIMIT $${params.length - 1} OFFSET $${params.length}`,
      params
    );

    return {
      items: result.rows,
      total: parseInt(countResult.rows[0].count)
    };
  }

  async create(data: Partial<Product>): Promise<Product> {
    const result = await this.db.query(
      `INSERT INTO products (id, name, slug, description, price, stock, category_id, images, is_active, created_at, updated_at)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW())
       RETURNING *`,
      [data.id, data.name, data.slug, data.description, data.price, data.stock, data.categoryId, JSON.stringify(data.images), data.isActive]
    );
    return result.rows[0];
  }

  async update(id: string, data: Partial<Product>): Promise<Product | null> {
    const fields: string[] = [];
    const params: any[] = [];
    let paramIndex = 1;

    for (const [key, value] of Object.entries(data)) {
      if (value !== undefined) {
        fields.push(`${this.toSnakeCase(key)} = $${paramIndex}`);
        params.push(key === 'images' ? JSON.stringify(value) : value);
        paramIndex++;
      }
    }

    if (fields.length === 0) return this.findById(id);

    fields.push(`updated_at = NOW()`);
    params.push(id);

    const result = await this.db.query(
      `UPDATE products SET ${fields.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
      params
    );
    return result.rows[0] || null;
  }

  async delete(id: string): Promise<boolean> {
    const result = await this.db.query(
      'UPDATE products SET is_active = false, updated_at = NOW() WHERE id = $1',
      [id]
    );
    return (result.rowCount ?? 0) > 0;
  }

  private toSnakeCase(str: string): string {
    return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
  }
}

Service:

typescript
// services/ProductService.ts
import { v4 as uuidv4 } from 'uuid';
import { ProductRepository } from '../repositories/ProductRepository';
import { CategoryRepository } from '../repositories/CategoryRepository';
import { CreateProductDTO, UpdateProductDTO, ProductResponseDTO, ProductListDTO } from '../dto';
import { NotFoundError, ValidationError } from '../errors';

export class ProductService {
  constructor(
    private productRepo: ProductRepository,
    private categoryRepo: CategoryRepository
  ) {}

  async getProduct(id: string): Promise<ProductResponseDTO> {
    const product = await this.productRepo.findById(id);
    if (!product) {
      throw new NotFoundError('Product', id);
    }

    const category = await this.categoryRepo.findById(product.categoryId);
    return this.toResponseDTO(product, category);
  }

  async listProducts(query: {
    page?: number;
    limit?: number;
    categoryId?: string;
    search?: string;
    sortBy?: string;
    sortOrder?: 'asc' | 'desc';
  }): Promise<ProductListDTO> {
    const page = Math.max(1, query.page || 1);
    const limit = Math.min(100, Math.max(1, query.limit || 20));

    const { items, total } = await this.productRepo.findAll({
      page,
      limit,
      categoryId: query.categoryId,
      search: query.search,
      sortBy: query.sortBy,
      sortOrder: query.sortOrder
    });

    const categories = await this.categoryRepo.findByIds(
      [...new Set(items.map(p => p.categoryId))]
    );
    const categoryMap = new Map(categories.map(c => [c.id, c]));

    return {
      items: items.map(p => this.toResponseDTO(p, categoryMap.get(p.categoryId))),
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit)
      }
    };
  }

  async createProduct(dto: CreateProductDTO): Promise<ProductResponseDTO> {
    // Is kurali: Fiyat 0'dan buyuk olmali
    if (dto.price <= 0) {
      throw new ValidationError('Price must be greater than 0');
    }

    // Is kurali: Kategori var mi kontrol et
    const category = await this.categoryRepo.findById(dto.categoryId);
    if (!category) {
      throw new ValidationError(`Category not found: ${dto.categoryId}`);
    }

    const product = await this.productRepo.create({
      id: uuidv4(),
      name: dto.name,
      slug: this.generateSlug(dto.name),
      description: dto.description,
      price: dto.price,
      stock: dto.stock,
      categoryId: dto.categoryId,
      images: dto.images || [],
      isActive: true
    });

    return this.toResponseDTO(product, category);
  }

  async updateProduct(id: string, dto: UpdateProductDTO): Promise<ProductResponseDTO> {
    const existing = await this.productRepo.findById(id);
    if (!existing) {
      throw new NotFoundError('Product', id);
    }

    if (dto.price !== undefined && dto.price <= 0) {
      throw new ValidationError('Price must be greater than 0');
    }

    const updated = await this.productRepo.update(id, {
      ...dto,
      slug: dto.name ? this.generateSlug(dto.name) : undefined
    });

    const category = await this.categoryRepo.findById(updated!.categoryId);
    return this.toResponseDTO(updated!, category);
  }

  async deleteProduct(id: string): Promise<void> {
    const existing = await this.productRepo.findById(id);
    if (!existing) {
      throw new NotFoundError('Product', id);
    }

    await this.productRepo.delete(id);
  }

  private toResponseDTO(product: any, category: any): ProductResponseDTO {
    return {
      id: product.id,
      name: product.name,
      slug: product.slug,
      description: product.description,
      price: product.price,
      formattedPrice: `${product.price.toLocaleString('tr-TR')} TL`,
      stock: product.stock,
      inStock: product.stock > 0,
      category: category
        ? { id: category.id, name: category.name }
        : { id: product.categoryId, name: 'Unknown' },
      images: product.images,
      createdAt: product.createdAt
    };
  }

  private generateSlug(name: string): string {
    return name
      .toLowerCase()
      .replace(/[^a-z0-9\s-]/g, '')
      .replace(/\s+/g, '-')
      .replace(/-+/g, '-')
      .trim();
  }
}

Controller:

typescript
// controllers/ProductController.ts
import { Request, Response, NextFunction } from 'express';
import { ProductService } from '../services/ProductService';
import { CreateProductDTO, UpdateProductDTO } from '../dto';

export class ProductController {
  constructor(private productService: ProductService) {}

  // GET /api/products/:id
  getProduct = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const product = await this.productService.getProduct(req.params.id);
      res.json({ success: true, data: product });
    } catch (error) {
      next(error);
    }
  };

  // GET /api/products
  listProducts = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const result = await this.productService.listProducts({
        page: parseInt(req.query.page as string) || 1,
        limit: parseInt(req.query.limit as string) || 20,
        categoryId: req.query.categoryId as string,
        search: req.query.search as string,
        sortBy: req.query.sortBy as string,
        sortOrder: req.query.sortOrder as 'asc' | 'desc'
      });
      res.json({ success: true, ...result });
    } catch (error) {
      next(error);
    }
  };

  // POST /api/products
  createProduct = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const dto: CreateProductDTO = req.body;
      const product = await this.productService.createProduct(dto);
      res.status(201).json({ success: true, data: product });
    } catch (error) {
      next(error);
    }
  };

  // PUT /api/products/:id
  updateProduct = async (req: Request, res: Response, next: NextFunction) => {
    try {
      const dto: UpdateProductDTO = req.body;
      const product = await this.productService.updateProduct(req.params.id, dto);
      res.json({ success: true, data: product });
    } catch (error) {
      next(error);
    }
  };

  // DELETE /api/products/:id
  deleteProduct = async (req: Request, res: Response, next: NextFunction) => {
    try {
      await this.productService.deleteProduct(req.params.id);
      res.status(204).send();
    } catch (error) {
      next(error);
    }
  };
}

Route Dosyasi:

typescript
// routes/product.routes.ts
import { Router } from 'express';
import { ProductController } from '../controllers/ProductController';
import { authMiddleware } from '../middlewares/authMiddleware';
import { adminMiddleware } from '../middlewares/adminMiddleware';
import { validate } from '../middlewares/validate';
import { createProductSchema, updateProductSchema } from '../validators/productValidator';

const router = Router();
const controller = new ProductController(productService);

router.get('/', controller.listProducts);
router.get('/:id', controller.getProduct);
router.post('/', authMiddleware, adminMiddleware, validate(createProductSchema), controller.createProduct);
router.put('/:id', authMiddleware, adminMiddleware, validate(updateProductSchema), controller.updateProduct);
router.delete('/:id', authMiddleware, adminMiddleware, controller.deleteProduct);

export { router as productRoutes };

2.4 DTO Pattern -- Neden Model Direkt Dondurulmez?

typescript
// YANLIS: Model direkt dondurme
app.get('/api/users/:id', async (req, res) => {
  const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
  res.json(user.rows[0]);
  // Sorun: password_hash, internal_notes gibi hassas alanlar da doner!
});

// DOGRU: DTO kullanarak sadece gerekli alanlari dondur
app.get('/api/users/:id', async (req, res) => {
  const user = await userService.getUser(req.params.id);
  res.json({
    success: true,
    data: {
      id: user.id,
      name: user.name,
      email: user.email,
      avatar: user.avatar,
      createdAt: user.createdAt
      // password_hash, internal_notes DONMEZ
    }
  });
});

DTO Kullanmanin Nedenleri:

NedenAçıklama
GüvenlikHassas alanlar (password, token) disari sizamaz
DecouplingDB semasi degisse API kontrati degismez
PerformansSadece gerekli alanlar serialize edilir
Versiyon yönetimiFarkli API versiyonlari için farkli DTO'lar
DokumantasyonDTO tipleri API'nin ne dondurdugunu net gosterir

2.5 Dependency Flow

Bagimlilik yonu her zaman yukari katmandan asagi katmana doğru olmalidir. Asla alt katman ust katmana bagimli olmamal.

Controller  ──depends on──>  Service  ──depends on──>  Repository
     │                          │                          │
     ▼                          ▼                          ▼
  Express/HTTP              Is mantigi              Database/ORM
  req, res, next            kurallar                query, CRUD

Yanlis Yonlu Bagimlilik:

typescript
// YANLIS: Repository'nin Service'e bagimliligi
class ProductRepository {
  constructor(private productService: ProductService) {} // YANLIS!
}

// YANLIS: Service'in Request objesine bagimliligi
class ProductService {
  async getProduct(req: Request) {} // YANLIS! HTTP bilgisi bilmemeli
}

Doğru Yonlu Bagimlilik:

typescript
// DOGRU: Service repository'e bagimli
class ProductService {
  constructor(private productRepo: ProductRepository) {} // DOGRU
}

// DOGRU: Controller service'e bagimli
class ProductController {
  constructor(private productService: ProductService) {} // DOGRU
}

3) Design Patterns (Tasarim Desenleri)

Design pattern'lar, yazilim gelistirmede tekrar eden sorunlara yonelik kanitlanmis cozumlerdir. Gang of Four (GoF) tarafindan Creational, Structural ve Behavioral olarak siniflandirilmistir.

3.1 Creational Patterns (Yaratimsal Desenler)

Singleton

Bir siniftan sadece bir tane instance olusturulmasini garanti eder. Global erisim noktasi sağlar.

Ne Zaman: Database connection, logger, configuration manager, cache client.

typescript
// Singleton -- Database Connection
class Database {
  private static instance: Database;
  private connection: any;

  private constructor() {
    // Private constructor -- disindan new yapilamaz
  }

  static getInstance(): Database {
    if (!Database.instance) {
      Database.instance = new Database();
    }
    return Database.instance;
  }

  async connect(connectionString: string) {
    if (!this.connection) {
      this.connection = await createConnection(connectionString);
    }
    return this.connection;
  }

  getConnection() {
    if (!this.connection) {
      throw new Error('Database not connected. Call connect() first.');
    }
    return this.connection;
  }
}

// Kullanim
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true -- ayni instance
typescript
// Modern JS/TS'de Singleton -- Module pattern ile daha basit
// database.ts
import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20
});

// ES Module zaten singleton gibi davranir
// Her import ayni pool instance'ini alir
export default pool;

// Kullanim
import pool from './database';
const result = await pool.query('SELECT * FROM users');

Factory

Nesne oluşturma mantigini bir fabrika sinifina/fonksiyonuna devreder. Hangi sinifin olusturulacagini parametreye gore belirler.

Ne Zaman: Bildirim gonderme (email/sms/push), odeme işleme (Stripe/PayPal/iyzico), dosya export (PDF/Excel/CSV).

typescript
// Factory -- Notification System
interface Notification {
  send(to: string, message: string): Promise<void>;
}

class EmailNotification implements Notification {
  async send(to: string, message: string) {
    console.log(`Email to ${to}: ${message}`);
    // await sendGrid.send({ to, subject: 'Notification', text: message });
  }
}

class SMSNotification implements Notification {
  async send(to: string, message: string) {
    console.log(`SMS to ${to}: ${message}`);
    // await twilio.messages.create({ to, body: message });
  }
}

class PushNotification implements Notification {
  async send(to: string, message: string) {
    console.log(`Push to ${to}: ${message}`);
    // await firebase.messaging().send({ token: to, notification: { body: message } });
  }
}

// Factory
class NotificationFactory {
  static create(type: 'email' | 'sms' | 'push'): Notification {
    switch (type) {
      case 'email': return new EmailNotification();
      case 'sms':   return new SMSNotification();
      case 'push':  return new PushNotification();
      default:
        throw new Error(`Unknown notification type: ${type}`);
    }
  }
}

// Kullanim
const notification = NotificationFactory.create('email');
await notification.send('user@example.com', 'Siparisiz hazirlandi!');

// Farkli kanallardan bildirim gonder
const channels: Array<'email' | 'sms' | 'push'> = ['email', 'push'];
for (const channel of channels) {
  const notifier = NotificationFactory.create(channel);
  await notifier.send(user.contact[channel], 'Siparisiz kargoya verildi!');
}

Builder

Karmasik nesneleri adim adim olusturur. Cok parametreli constructor yerine okunabilir bir API sağlar.

Ne Zaman: Karmasik query oluşturma, email/rapor oluşturma, konfigürasyon nesneleri.

typescript
// Builder -- Query Builder
class QueryBuilder {
  private table: string = '';
  private conditions: string[] = [];
  private orderByClause: string = '';
  private limitValue: number | null = null;
  private offsetValue: number | null = null;
  private selectedFields: string[] = ['*'];
  private joinClauses: string[] = [];
  private params: any[] = [];

  from(table: string): this {
    this.table = table;
    return this;
  }

  select(...fields: string[]): this {
    this.selectedFields = fields;
    return this;
  }

  where(condition: string, value: any): this {
    this.params.push(value);
    this.conditions.push(`${condition} $${this.params.length}`);
    return this;
  }

  join(table: string, on: string): this {
    this.joinClauses.push(`JOIN ${table} ON ${on}`);
    return this;
  }

  orderBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
    this.orderByClause = `ORDER BY ${field} ${direction}`;
    return this;
  }

  limit(value: number): this {
    this.limitValue = value;
    return this;
  }

  offset(value: number): this {
    this.offsetValue = value;
    return this;
  }

  build(): { sql: string; params: any[] } {
    let sql = `SELECT ${this.selectedFields.join(', ')} FROM ${this.table}`;

    if (this.joinClauses.length > 0) {
      sql += ` ${this.joinClauses.join(' ')}`;
    }
    if (this.conditions.length > 0) {
      sql += ` WHERE ${this.conditions.join(' AND ')}`;
    }
    if (this.orderByClause) {
      sql += ` ${this.orderByClause}`;
    }
    if (this.limitValue !== null) {
      sql += ` LIMIT ${this.limitValue}`;
    }
    if (this.offsetValue !== null) {
      sql += ` OFFSET ${this.offsetValue}`;
    }

    return { sql, params: this.params };
  }
}

// Kullanim -- okunabilir ve esnek
const query = new QueryBuilder()
  .from('products')
  .select('products.id', 'products.name', 'categories.name AS category')
  .join('categories', 'categories.id = products.category_id')
  .where('products.price >', 100)
  .where('products.is_active =', true)
  .orderBy('products.created_at', 'DESC')
  .limit(20)
  .offset(0)
  .build();

// query.sql = "SELECT products.id, products.name, categories.name AS category
//              FROM products JOIN categories ON categories.id = products.category_id
//              WHERE products.price > $1 AND products.is_active = $2
//              ORDER BY products.created_at DESC LIMIT 20 OFFSET 0"
// query.params = [100, true]

3.2 Structural Patterns (Yapisal Desenler)

Adapter

Uyumsuz iki arayuzu birbiriyle calismaya uyumlu hale getirir. Ucuncu parti kütüphaneleri kendi interface'ine sararsin.

Ne Zaman: Ucuncu parti SDK degisikligi, farkli odeme saglayicilari, farkli email servisleri.

typescript
// Adapter -- Farkli odeme saglayicilarini tek interface altinda birlestir

// Ortak interface
interface PaymentGateway {
  charge(amount: number, currency: string, source: string): Promise<PaymentResult>;
  refund(transactionId: string, amount: number): Promise<RefundResult>;
}

interface PaymentResult {
  transactionId: string;
  status: 'success' | 'failed';
  amount: number;
}

interface RefundResult {
  refundId: string;
  status: 'success' | 'failed';
}

// Stripe Adapter
class StripeAdapter implements PaymentGateway {
  private stripe: Stripe;

  constructor(apiKey: string) {
    this.stripe = new Stripe(apiKey);
  }

  async charge(amount: number, currency: string, source: string): Promise<PaymentResult> {
    const result = await this.stripe.paymentIntents.create({
      amount: amount * 100, // Stripe kurusu cinsinden bekler
      currency,
      payment_method: source,
      confirm: true
    });

    return {
      transactionId: result.id,
      status: result.status === 'succeeded' ? 'success' : 'failed',
      amount: result.amount / 100
    };
  }

  async refund(transactionId: string, amount: number): Promise<RefundResult> {
    const result = await this.stripe.refunds.create({
      payment_intent: transactionId,
      amount: amount * 100
    });

    return {
      refundId: result.id,
      status: result.status === 'succeeded' ? 'success' : 'failed'
    };
  }
}

// iyzico Adapter
class IyzicoAdapter implements PaymentGateway {
  private iyzico: any;

  constructor(apiKey: string, secretKey: string) {
    this.iyzico = new Iyzipay({ apiKey, secretKey, uri: 'https://api.iyzipay.com' });
  }

  async charge(amount: number, currency: string, source: string): Promise<PaymentResult> {
    // iyzico farkli bir API yapisina sahip
    const request = {
      price: amount.toString(),
      paidPrice: amount.toString(),
      currency: currency === 'TRY' ? 'TRY' : currency,
      paymentCard: { cardToken: source }
    };

    const result = await this.iyzico.payment.create(request);

    return {
      transactionId: result.paymentId,
      status: result.status === 'success' ? 'success' : 'failed',
      amount
    };
  }

  async refund(transactionId: string, amount: number): Promise<RefundResult> {
    const result = await this.iyzico.refund.create({
      paymentTransactionId: transactionId,
      price: amount.toString()
    });

    return {
      refundId: result.paymentId,
      status: result.status === 'success' ? 'success' : 'failed'
    };
  }
}

// Kullanim -- hangi provider kullanildigini bilmez
class PaymentService {
  constructor(private gateway: PaymentGateway) {}

  async processPayment(orderId: string, amount: number, cardToken: string) {
    const result = await this.gateway.charge(amount, 'TRY', cardToken);
    if (result.status === 'failed') {
      throw new PaymentError('Payment failed');
    }
    return result;
  }
}

// Stripe ile
const stripeService = new PaymentService(new StripeAdapter(STRIPE_KEY));

// iyzico ile -- kod degismeden provider degisir
const iyzicoService = new PaymentService(new IyzicoAdapter(IYZICO_KEY, IYZICO_SECRET));

Decorator

Mevcut bir nesneye dinamik olarak yeni davranislar ekler. Kalitim yerine composition kullanir.

Ne Zaman: Loglama eklemek, cache eklemek, yetki kontrolu, performans olcme.

typescript
// Decorator -- Service'e loglama ve cache ekle

interface IUserService {
  getUser(id: string): Promise<User>;
  updateUser(id: string, data: Partial<User>): Promise<User>;
}

// Temel servis
class UserService implements IUserService {
  async getUser(id: string): Promise<User> {
    return await db.query('SELECT * FROM users WHERE id = $1', [id]);
  }

  async updateUser(id: string, data: Partial<User>): Promise<User> {
    return await db.query('UPDATE users SET ... WHERE id = $1 RETURNING *', [id]);
  }
}

// Logging Decorator
class LoggingUserService implements IUserService {
  constructor(private wrapped: IUserService) {}

  async getUser(id: string): Promise<User> {
    console.log(`[UserService] getUser called with id: ${id}`);
    const start = Date.now();
    const result = await this.wrapped.getUser(id);
    console.log(`[UserService] getUser completed in ${Date.now() - start}ms`);
    return result;
  }

  async updateUser(id: string, data: Partial<User>): Promise<User> {
    console.log(`[UserService] updateUser called with id: ${id}`);
    const result = await this.wrapped.updateUser(id, data);
    console.log(`[UserService] updateUser completed`);
    return result;
  }
}

// Cache Decorator
class CachedUserService implements IUserService {
  constructor(
    private wrapped: IUserService,
    private cache: Map<string, { data: User; expiry: number }> = new Map()
  ) {}

  async getUser(id: string): Promise<User> {
    const cached = this.cache.get(id);
    if (cached && cached.expiry > Date.now()) {
      console.log(`[Cache] Hit for user ${id}`);
      return cached.data;
    }

    console.log(`[Cache] Miss for user ${id}`);
    const user = await this.wrapped.getUser(id);
    this.cache.set(id, { data: user, expiry: Date.now() + 60000 }); // 1 dk cache
    return user;
  }

  async updateUser(id: string, data: Partial<User>): Promise<User> {
    const user = await this.wrapped.updateUser(id, data);
    this.cache.delete(id); // Cache invalidation
    return user;
  }
}

// Kullanim -- decorator'lari ust uste sar
const userService = new LoggingUserService(
  new CachedUserService(
    new UserService()
  )
);

// Cagirdiginda: Logging -> Cache kontrol -> (miss ise) DB sorgusu
const user = await userService.getUser('user-123');

Facade

Karmasik bir alt sistemi basit bir arayuz arkasina gizler.

Ne Zaman: Karmasik 3. parti API'leri basitlestirme, birden fazla servisi koordine etme.

typescript
// Facade -- Siparis islemi birden fazla servisi koordine eder
class OrderFacade {
  constructor(
    private productService: ProductService,
    private inventoryService: InventoryService,
    private paymentService: PaymentService,
    private shippingService: ShippingService,
    private notificationService: NotificationService
  ) {}

  // Disaridan tek bir metot cagirilir, karmasiklik icerde gizlenir
  async placeOrder(userId: string, items: CartItem[], paymentInfo: PaymentInfo): Promise<Order> {
    // 1. Urunleri dogrula
    const products = await this.productService.validateProducts(items);

    // 2. Stok kontrol
    await this.inventoryService.checkAvailability(items);

    // 3. Toplam tutar hesapla
    const total = this.calculateTotal(products, items);

    // 4. Odeme al
    const payment = await this.paymentService.charge(total, paymentInfo);

    // 5. Stoktan dus
    await this.inventoryService.reserveStock(items);

    // 6. Kargo olustur
    const shipping = await this.shippingService.createShipment(userId, items);

    // 7. Bildirim gonder
    await this.notificationService.sendOrderConfirmation(userId, {
      orderId: payment.transactionId,
      total,
      estimatedDelivery: shipping.estimatedDate
    });

    return {
      id: payment.transactionId,
      items: products,
      total,
      shippingId: shipping.id,
      status: 'confirmed'
    };
  }

  private calculateTotal(products: Product[], items: CartItem[]): number {
    return items.reduce((sum, item) => {
      const product = products.find(p => p.id === item.productId)!;
      return sum + product.price * item.quantity;
    }, 0);
  }
}

// Kullanim -- tek satirda karmasik siparis islemi
const orderFacade = new OrderFacade(productService, inventoryService, paymentService, shippingService, notificationService);
const order = await orderFacade.placeOrder(userId, cartItems, paymentInfo);

Proxy

Bir nesneye erisimi kontrol eder. Lazy loading, access control, logging, caching için kullanilir.

typescript
// Proxy -- API Rate Limiter
class RateLimitedApiClient {
  private requestCounts: Map<string, { count: number; resetAt: number }> = new Map();
  private maxRequests = 100; // dakikada max istek
  private windowMs = 60000; // 1 dakika

  constructor(private apiClient: HttpClient) {}

  async request(endpoint: string, options: RequestOptions): Promise<Response> {
    const now = Date.now();
    const key = endpoint;
    const record = this.requestCounts.get(key);

    if (record && record.resetAt > now && record.count >= this.maxRequests) {
      throw new RateLimitError(
        `Rate limit exceeded for ${endpoint}. Try again in ${Math.ceil((record.resetAt - now) / 1000)}s`
      );
    }

    if (!record || record.resetAt <= now) {
      this.requestCounts.set(key, { count: 1, resetAt: now + this.windowMs });
    } else {
      record.count++;
    }

    return this.apiClient.request(endpoint, options);
  }
}

3.3 Behavioral Patterns (Davranissal Desenler)

Observer (Event Emitter)

Bir nesnenin durumu degistiginde bagli tüm dinleyicileri (subscriber) otomatik bilgilendirir. Event-driven mimarilerin temeli.

Ne Zaman: Kullanici kaydi sonrasi (email gonder, log yaz, analytics), siparis durumu degisikligi, real-time bildirimler.

typescript
// Observer -- Event System
type EventHandler = (...args: any[]) => void | Promise<void>;

class EventEmitter {
  private listeners: Map<string, EventHandler[]> = new Map();

  on(event: string, handler: EventHandler): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event)!.push(handler);
  }

  off(event: string, handler: EventHandler): void {
    const handlers = this.listeners.get(event);
    if (handlers) {
      this.listeners.set(event, handlers.filter(h => h !== handler));
    }
  }

  async emit(event: string, ...args: any[]): Promise<void> {
    const handlers = this.listeners.get(event) || [];
    await Promise.all(handlers.map(handler => handler(...args)));
  }
}

// Kullanim -- Kullanici kaydi sonrasi olaylar
const eventBus = new EventEmitter();

// Dinleyiciler (subscribers)
eventBus.on('user.registered', async (user) => {
  await emailService.sendWelcomeEmail(user.email, user.name);
});

eventBus.on('user.registered', async (user) => {
  await analyticsService.track('user_signup', { userId: user.id, source: user.source });
});

eventBus.on('user.registered', async (user) => {
  await slackService.notify(`Yeni kullanici: ${user.name} (${user.email})`);
});

// Tetikleme (publisher)
class UserService {
  constructor(private eventBus: EventEmitter) {}

  async register(data: RegisterDTO) {
    const user = await userRepo.create(data);

    // Kayit sonrasi olaylari tetikle
    await this.eventBus.emit('user.registered', user);

    return user;
  }
}
typescript
// Node.js built-in EventEmitter ile
import { EventEmitter } from 'events';

class OrderService extends EventEmitter {
  async createOrder(data: CreateOrderDTO) {
    const order = await orderRepo.create(data);

    this.emit('order:created', order);
    return order;
  }

  async cancelOrder(orderId: string) {
    const order = await orderRepo.updateStatus(orderId, 'cancelled');

    this.emit('order:cancelled', order);
    return order;
  }
}

const orderService = new OrderService();

orderService.on('order:created', (order) => {
  emailService.sendOrderConfirmation(order);
});

orderService.on('order:cancelled', (order) => {
  paymentService.refund(order.paymentId);
  inventoryService.restoreStock(order.items);
});

Strategy

Algoritma ailesini tanimlar ve her birini degistirilebilir hale getirir. Runtime'da algoritmalar arasinda gecis yapılabilir.

Ne Zaman: Farkli fiyatlandirma stratejileri, farkli siralama algoritmalari, farkli dosya sıkistirma yontemleri.

typescript
// Strategy -- Fiyatlandirma stratejileri
interface PricingStrategy {
  calculate(basePrice: number, context: PricingContext): number;
}

interface PricingContext {
  quantity: number;
  customerType: 'regular' | 'premium' | 'wholesale';
  couponCode?: string;
}

class RegularPricing implements PricingStrategy {
  calculate(basePrice: number, context: PricingContext): number {
    return basePrice * context.quantity;
  }
}

class PremiumPricing implements PricingStrategy {
  calculate(basePrice: number, context: PricingContext): number {
    const discount = 0.15; // %15 indirim
    return basePrice * context.quantity * (1 - discount);
  }
}

class WholesalePricing implements PricingStrategy {
  calculate(basePrice: number, context: PricingContext): number {
    let discount = 0;
    if (context.quantity >= 100) discount = 0.30;
    else if (context.quantity >= 50) discount = 0.20;
    else if (context.quantity >= 10) discount = 0.10;

    return basePrice * context.quantity * (1 - discount);
  }
}

class PricingService {
  private strategies: Map<string, PricingStrategy> = new Map();

  constructor() {
    this.strategies.set('regular', new RegularPricing());
    this.strategies.set('premium', new PremiumPricing());
    this.strategies.set('wholesale', new WholesalePricing());
  }

  calculatePrice(basePrice: number, context: PricingContext): number {
    const strategy = this.strategies.get(context.customerType);
    if (!strategy) {
      throw new Error(`Unknown customer type: ${context.customerType}`);
    }
    return strategy.calculate(basePrice, context);
  }
}

// Kullanim
const pricingService = new PricingService();

// Ayni urun, farkli musteri tipleri
const regularPrice = pricingService.calculatePrice(100, { quantity: 5, customerType: 'regular' });
// 500

const premiumPrice = pricingService.calculatePrice(100, { quantity: 5, customerType: 'premium' });
// 425 (%15 indirimli)

const wholesalePrice = pricingService.calculatePrice(100, { quantity: 50, customerType: 'wholesale' });
// 4000 (%20 indirimli)

Command

Bir islemi nesne olarak kapsuller. Geri alma (undo), kuyruge alma, loglama için kullanilir.

typescript
// Command -- Undo/Redo destekli metin editoru
interface Command {
  execute(): void;
  undo(): void;
}

class InsertTextCommand implements Command {
  constructor(
    private document: TextDocument,
    private position: number,
    private text: string
  ) {}

  execute(): void {
    this.document.insert(this.position, this.text);
  }

  undo(): void {
    this.document.delete(this.position, this.text.length);
  }
}

class DeleteTextCommand implements Command {
  private deletedText: string = '';

  constructor(
    private document: TextDocument,
    private position: number,
    private length: number
  ) {}

  execute(): void {
    this.deletedText = this.document.getText(this.position, this.length);
    this.document.delete(this.position, this.length);
  }

  undo(): void {
    this.document.insert(this.position, this.deletedText);
  }
}

class CommandHistory {
  private history: Command[] = [];
  private undone: Command[] = [];

  execute(command: Command): void {
    command.execute();
    this.history.push(command);
    this.undone = []; // Yeni komut sonrasi redo gecmisi temizlenir
  }

  undo(): void {
    const command = this.history.pop();
    if (command) {
      command.undo();
      this.undone.push(command);
    }
  }

  redo(): void {
    const command = this.undone.pop();
    if (command) {
      command.execute();
      this.history.push(command);
    }
  }
}

State

Bir nesnenin davranisini ic durumuna gore degistirir. if/else zincirleri yerine her durumu ayri bir sınıf olarak modeller.

typescript
// State -- Siparis durumu yonetimi
interface OrderState {
  name: string;
  confirm(order: Order): void;
  ship(order: Order): void;
  deliver(order: Order): void;
  cancel(order: Order): void;
}

class PendingState implements OrderState {
  name = 'pending';

  confirm(order: Order): void {
    console.log('Siparis onaylandi');
    order.setState(new ConfirmedState());
  }

  ship(order: Order): void {
    throw new Error('Onaylanmamis siparis kargolanamaz');
  }

  deliver(order: Order): void {
    throw new Error('Onaylanmamis siparis teslim edilemez');
  }

  cancel(order: Order): void {
    console.log('Siparis iptal edildi');
    order.setState(new CancelledState());
  }
}

class ConfirmedState implements OrderState {
  name = 'confirmed';

  confirm(order: Order): void {
    throw new Error('Siparis zaten onaylandi');
  }

  ship(order: Order): void {
    console.log('Siparis kargoya verildi');
    order.setState(new ShippedState());
  }

  deliver(order: Order): void {
    throw new Error('Kargoya verilmeden teslim edilemez');
  }

  cancel(order: Order): void {
    console.log('Onaylanmis siparis iptal edildi, iade basladi');
    order.setState(new CancelledState());
  }
}

class ShippedState implements OrderState {
  name = 'shipped';

  confirm(order: Order): void {
    throw new Error('Kargodaki siparis tekrar onaylanamaz');
  }

  ship(order: Order): void {
    throw new Error('Siparis zaten kargoda');
  }

  deliver(order: Order): void {
    console.log('Siparis teslim edildi');
    order.setState(new DeliveredState());
  }

  cancel(order: Order): void {
    throw new Error('Kargodaki siparis iptal edilemez');
  }
}

class DeliveredState implements OrderState {
  name = 'delivered';

  confirm(order: Order): void { throw new Error('Teslim edilmis siparis onaylanamaz'); }
  ship(order: Order): void { throw new Error('Teslim edilmis siparis kargolanamaz'); }
  deliver(order: Order): void { throw new Error('Siparis zaten teslim edildi'); }
  cancel(order: Order): void { throw new Error('Teslim edilmis siparis iptal edilemez. Iade talebi olusturun.'); }
}

class CancelledState implements OrderState {
  name = 'cancelled';

  confirm(order: Order): void { throw new Error('Iptal edilmis siparis onaylanamaz'); }
  ship(order: Order): void { throw new Error('Iptal edilmis siparis kargolanamaz'); }
  deliver(order: Order): void { throw new Error('Iptal edilmis siparis teslim edilemez'); }
  cancel(order: Order): void { throw new Error('Siparis zaten iptal edildi'); }
}

class Order {
  private state: OrderState = new PendingState();

  setState(state: OrderState): void {
    this.state = state;
  }

  getState(): string {
    return this.state.name;
  }

  confirm(): void { this.state.confirm(this); }
  ship(): void { this.state.ship(this); }
  deliver(): void { this.state.deliver(this); }
  cancel(): void { this.state.cancel(this); }
}

// Kullanim
const order = new Order();
console.log(order.getState()); // 'pending'

order.confirm();               // 'Siparis onaylandi'
console.log(order.getState()); // 'confirmed'

order.ship();                  // 'Siparis kargoya verildi'
console.log(order.getState()); // 'shipped'

// order.cancel(); // Error: Kargodaki siparis iptal edilemez

order.deliver();               // 'Siparis teslim edildi'
console.log(order.getState()); // 'delivered'

3.4 Anti-Patterns (Kotu Desenler)

Kacininmasi gereken yaygin hatali yaklasimlar:

Anti-PatternAçıklamaÇözüm
God ObjectTek bir sınıf her seyi yapar (3000+ satir, 50+ metot)Single Responsibility -- sinifi sorumluluga gore bol
Spaghetti CodeDallanma, atlama, ic ice if/else, goto benzeri akisFonksiyonlara ayir, early return kullan, state pattern
Golden HammerHer soruna ayni araci/teknolojiyi uygulamaDoğru araci doğru is için seç
Copy-Paste ProgrammingKodu kopyala-yapistir ile cozmekDRY -- ortak fonksiyon/sınıf cikar
Premature OptimizationIhtiyac olmadan erken optimizasyon"Make it work, make it right, make it fast"
Magic Numbers/StringsKodda aciklamasiz sabit degerlerConstants/enums kullan
Shotgun SurgeryBir degisiklik için 20 farkli dosya değiştirmeIliskili kodu bir arada tut (cohesion)
Feature EnvyBir sınıf baska sinifin verisine cok erisirMetodu verinin oldugu sinifa tasi
Dead CodeKullanilmayan ama silinnmeyen kodlarSil. Version control'den geri getirebilirsin

4) SOLID Prensipleri

SOLID, Robert C. Martin (Uncle Bob) tarafindan tanimlanan 5 nesne yonelimli tasarim prensibidir. Yazilimin bakim yapilebilir, esnek ve genisletilebilir olmasini sağlar.

4.1 S -- Single Responsibility Principle (Tek Sorumluluk)

Bir sinifin degismek için tek bir nedeni olmali.

Ihlal Ornegi:

typescript
// YANLIS: Tek sinif her seyi yapar
class UserManager {
  async createUser(data: UserData) {
    // Validation
    if (!data.email.includes('@')) throw new Error('Invalid email');
    if (data.password.length < 8) throw new Error('Password too short');

    // Password hashing
    const hashedPassword = await bcrypt.hash(data.password, 10);

    // DB insert
    const user = await db.query(
      'INSERT INTO users (email, password) VALUES ($1, $2) RETURNING *',
      [data.email, hashedPassword]
    );

    // Email gonder
    await nodemailer.createTransport({...}).sendMail({
      to: data.email,
      subject: 'Hosgeldiniz',
      html: `<h1>Merhaba ${data.name}</h1>`
    });

    // Log yaz
    fs.appendFileSync('app.log', `User created: ${data.email}\n`);

    return user;
  }
}
// Bu sinifin degismesi icin 5 farkli neden var:
// validation kurali degisirse, hashing degisirse, DB semasi degisirse,
// email sablonu degisirse, log formati degisirse

Doğru Ornegi:

typescript
// DOGRU: Her sinifin tek sorumlulugu var
class UserValidator {
  validate(data: UserData): void {
    if (!data.email.includes('@')) throw new ValidationError('Invalid email');
    if (data.password.length < 8) throw new ValidationError('Password too short');
  }
}

class PasswordHasher {
  async hash(password: string): Promise<string> {
    return bcrypt.hash(password, 10);
  }
}

class UserRepository {
  async create(email: string, hashedPassword: string): Promise<User> {
    const result = await db.query(
      'INSERT INTO users (email, password) VALUES ($1, $2) RETURNING *',
      [email, hashedPassword]
    );
    return result.rows[0];
  }
}

class WelcomeEmailSender {
  async send(user: User): Promise<void> {
    await this.mailer.sendMail({
      to: user.email,
      subject: 'Hosgeldiniz',
      html: `<h1>Merhaba ${user.name}</h1>`
    });
  }
}

class UserService {
  constructor(
    private validator: UserValidator,
    private hasher: PasswordHasher,
    private userRepo: UserRepository,
    private emailSender: WelcomeEmailSender,
    private logger: Logger
  ) {}

  async createUser(data: UserData): Promise<User> {
    this.validator.validate(data);
    const hashedPassword = await this.hasher.hash(data.password);
    const user = await this.userRepo.create(data.email, hashedPassword);
    await this.emailSender.send(user);
    this.logger.info('User created', { userId: user.id });
    return user;
  }
}

4.2 O -- Open/Closed Principle (Acik/Kapali)

Siniflar genisletmeye acik, degistirmeye kapali olmali.

Ihlal Ornegi:

typescript
// YANLIS: Yeni odeme yontemi eklemek icin mevcut kodu degistirmek lazim
class PaymentProcessor {
  process(method: string, amount: number) {
    if (method === 'credit_card') {
      // Kredi karti islemi
      return this.chargeCreditCard(amount);
    } else if (method === 'paypal') {
      // PayPal islemi
      return this.chargePayPal(amount);
    } else if (method === 'bitcoin') {
      // Her yeni odeme yontemi icin buraya if eklenmeli
      return this.chargeBitcoin(amount);
    }
    throw new Error('Unknown payment method');
  }
}

Doğru Ornegi:

typescript
// DOGRU: Yeni odeme yontemi eklemek icin mevcut kodu degistirmene gerek yok
interface PaymentMethod {
  name: string;
  charge(amount: number): Promise<PaymentResult>;
}

class CreditCardPayment implements PaymentMethod {
  name = 'credit_card';
  async charge(amount: number): Promise<PaymentResult> {
    // Kredi karti islemi
    return { success: true, transactionId: '...' };
  }
}

class PayPalPayment implements PaymentMethod {
  name = 'paypal';
  async charge(amount: number): Promise<PaymentResult> {
    // PayPal islemi
    return { success: true, transactionId: '...' };
  }
}

// Yeni odeme yontemi ekle -- mevcut kod degismez
class CryptoPayment implements PaymentMethod {
  name = 'crypto';
  async charge(amount: number): Promise<PaymentResult> {
    return { success: true, transactionId: '...' };
  }
}

class PaymentProcessor {
  private methods: Map<string, PaymentMethod> = new Map();

  register(method: PaymentMethod): void {
    this.methods.set(method.name, method);
  }

  async process(methodName: string, amount: number): Promise<PaymentResult> {
    const method = this.methods.get(methodName);
    if (!method) throw new Error(`Unknown payment method: ${methodName}`);
    return method.charge(amount);
  }
}

// Kullanim
const processor = new PaymentProcessor();
processor.register(new CreditCardPayment());
processor.register(new PayPalPayment());
processor.register(new CryptoPayment()); // Yeni -- hicbir sey degistirmeden eklendi

4.3 L -- Liskov Substitution Principle (Liskov Yer Degistirme)

Alt siniflar, ust sinifin yerine kullanilabilmeli. Davranis degistirmemeli.

Ihlal Ornegi:

typescript
// YANLIS: Square, Rectangle'in davranisini bozar
class Rectangle {
  constructor(protected width: number, protected height: number) {}

  setWidth(w: number) { this.width = w; }
  setHeight(h: number) { this.height = h; }
  getArea(): number { return this.width * this.height; }
}

class Square extends Rectangle {
  setWidth(w: number) {
    this.width = w;
    this.height = w; // Kare oldugu icin ikisini de degistir
  }

  setHeight(h: number) {
    this.width = h;
    this.height = h;
  }
}

// Problem: Rectangle bekleyen kod Square ile bozulur
function resizeRectangle(rect: Rectangle) {
  rect.setWidth(10);
  rect.setHeight(5);
  console.log(rect.getArea()); // Rectangle icin 50 bekleriz
  // Ama Square verirsen: setHeight(5) width'i de 5 yapar -> 25 doner!
}

Doğru Ornegi:

typescript
// DOGRU: Ortak interface kullan, kalitim zorlama
interface Shape {
  getArea(): number;
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  getArea(): number { return this.width * this.height; }
}

class Square implements Shape {
  constructor(private side: number) {}
  getArea(): number { return this.side * this.side; }
}

// Her ikisi de Shape olarak kullanilabilir, davranis tutarli
function printArea(shape: Shape) {
  console.log(`Area: ${shape.getArea()}`);
}

printArea(new Rectangle(10, 5)); // 50
printArea(new Square(5));         // 25

4.4 I -- Interface Segregation Principle (Arayuz Ayirma)

Istemciler kullanmadiklari metotlara bagimli olmaya zorlanmamali.

Ihlal Ornegi:

typescript
// YANLIS: Sisme interface -- her sinif kullanmadigi metotlari implement etmek zorunda
interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
  attendMeeting(): void;
  writeReport(): void;
}

class Developer implements Worker {
  work() { console.log('Coding...'); }
  eat() { console.log('Eating...'); }
  sleep() { console.log('Sleeping...'); }
  attendMeeting() { console.log('In meeting...'); }
  writeReport() { console.log('Writing report...'); }
}

class Robot implements Worker {
  work() { console.log('Working...'); }
  eat() { throw new Error('Robots do not eat!'); }         // YANLIS!
  sleep() { throw new Error('Robots do not sleep!'); }     // YANLIS!
  attendMeeting() { throw new Error('Not applicable!'); }  // YANLIS!
  writeReport() { throw new Error('Not applicable!'); }    // YANLIS!
}

Doğru Ornegi:

typescript
// DOGRU: Kucuk, amaca yonelik interface'ler
interface Workable {
  work(): void;
}

interface Feedable {
  eat(): void;
}

interface Sleepable {
  sleep(): void;
}

interface Meetable {
  attendMeeting(): void;
}

class Developer implements Workable, Feedable, Sleepable, Meetable {
  work() { console.log('Coding...'); }
  eat() { console.log('Eating...'); }
  sleep() { console.log('Sleeping...'); }
  attendMeeting() { console.log('In meeting...'); }
}

class Robot implements Workable {
  work() { console.log('Working...'); }
  // Sadece ilgili interface'i implement eder, gereksiz metot yok
}

4.5 D -- Dependency Inversion Principle (Bagimlilik Tersine Cevirme)

Ust seviye moduller alt seviye modullere bagimli olmamali. Her ikisi de soyutlamalara (abstractions) bagimli olmali.

Ihlal Ornegi:

typescript
// YANLIS: UserService dogrudan PostgreSQL'e bagimli
import { Pool } from 'pg';

class UserService {
  private db: Pool;

  constructor() {
    this.db = new Pool({ connectionString: '...' }); // Dogrudan bagimlilik
  }

  async getUser(id: string) {
    return this.db.query('SELECT * FROM users WHERE id = $1', [id]);
  }
}
// Sorun: MongoDB'ye gecmek istesek tum service'i degistirmek gerekir
// Sorun: Test ederken gercek DB lazim

Doğru Ornegi:

typescript
// DOGRU: Interface'e bagimli ol, concrete sinifi disaridan al
interface IUserRepository {
  findById(id: string): Promise<User | null>;
  create(data: CreateUserDTO): Promise<User>;
  update(id: string, data: Partial<User>): Promise<User>;
  delete(id: string): Promise<boolean>;
}

// PostgreSQL implementasyonu
class PostgresUserRepository implements IUserRepository {
  constructor(private db: Pool) {}

  async findById(id: string): Promise<User | null> {
    const result = await this.db.query('SELECT * FROM users WHERE id = $1', [id]);
    return result.rows[0] || null;
  }

  async create(data: CreateUserDTO): Promise<User> {
    const result = await this.db.query(
      'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
      [data.name, data.email]
    );
    return result.rows[0];
  }

  async update(id: string, data: Partial<User>): Promise<User> { /* ... */ return {} as User; }
  async delete(id: string): Promise<boolean> { /* ... */ return true; }
}

// MongoDB implementasyonu
class MongoUserRepository implements IUserRepository {
  constructor(private collection: Collection) {}

  async findById(id: string): Promise<User | null> {
    return this.collection.findOne({ _id: new ObjectId(id) });
  }

  async create(data: CreateUserDTO): Promise<User> {
    const result = await this.collection.insertOne(data);
    return { id: result.insertedId.toString(), ...data } as User;
  }

  async update(id: string, data: Partial<User>): Promise<User> { /* ... */ return {} as User; }
  async delete(id: string): Promise<boolean> { /* ... */ return true; }
}

// Service interface'e bagimli, concrete sinifi bilmiyor
class UserService {
  constructor(private userRepo: IUserRepository) {}

  async getUser(id: string): Promise<User> {
    const user = await this.userRepo.findById(id);
    if (!user) throw new NotFoundError('User', id);
    return user;
  }
}

// Production: PostgreSQL
const userService = new UserService(new PostgresUserRepository(pgPool));

// Test: Mock/Fake repository
const mockRepo: IUserRepository = {
  findById: async (id) => ({ id, name: 'Test', email: 'test@test.com' } as User),
  create: async (data) => ({ id: '1', ...data } as User),
  update: async (id, data) => ({ id, ...data } as User),
  delete: async () => true
};
const testService = new UserService(mockRepo);

5) Clean Code Prensipleri

Clean code, baskalarinin (ve gelecekteki senin) kolayca okuyabilecegi, anlayabilecegi ve degistirebilecegi kodtur.

5.1 Naming Conventions (Adlandirma Kurallari)

typescript
// YANLIS adlandirma
const d = 5;                    // d ne?
const list = getUsers();         // hangi list?
function proc(x: any) {}        // ne isliyor?
class Manager {}                 // neyi manage ediyor?
const flag = true;               // hangi flag?
function handle() {}             // neyi handle ediyor?

// DOGRU adlandirma
const maxRetryCount = 5;
const activeUsers = getActiveUsers();
function validateEmail(email: string): boolean {}
class PaymentProcessor {}
const isEmailVerified = true;
function handlePaymentFailure(error: PaymentError) {}

Adlandirma Kurallari Tablosu:

OggeKuralIyi ÖrnekKotu Örnek
DeğişkencamelCase, anlamli isimuserCount, isActiveuc, flag
FonksiyoncamelCase, fiil ile başlagetUser(), calculateTotal()user(), total()
SınıfPascalCase, isimUserService, OrderControllerusrSvc, ctrl
InterfacePascalCase, I prefix (opsiyonel)IUserRepository, Cacheableiur, cache
ConstantUPPER_SNAKE_CASEMAX_RETRY_COUNT, API_BASE_URLmaxRetry, url
Booleanis/has/can/should ile başlaisActive, hasPermissionactive, permission
Dosyakebab-case veya camelCaseuser-service.ts, userService.tsUS.ts, temp.ts
EnumPascalCaseOrderStatus.Pendingstatus.p

5.2 Fonksiyon Boyutu

Bir fonksiyon:

  • Maksimum 20-30 satir olmali
  • Tek bir is yapmali
  • Ayni soyutlama seviyesinde işlem yapmali
typescript
// YANLIS: 80+ satirlik dev fonksiyon
async function processOrder(orderData: any) {
  // Validate
  if (!orderData.items) throw new Error('...');
  if (!orderData.userId) throw new Error('...');
  if (!orderData.paymentMethod) throw new Error('...');
  // ... 20 satir daha validation

  // Calculate total
  let total = 0;
  for (const item of orderData.items) {
    const product = await db.query('...');
    total += product.price * item.quantity;
    if (item.coupon) {
      // ... 15 satir kupon hesaplama
    }
  }

  // Process payment
  // ... 20 satir odeme islemi

  // Update inventory
  // ... 15 satir stok guncelleme

  // Send notifications
  // ... 10 satir email/sms
}

// DOGRU: Kucuk, odakli fonksiyonlar
async function processOrder(orderData: OrderInput): Promise<Order> {
  validateOrder(orderData);

  const items = await resolveProducts(orderData.items);
  const total = calculateOrderTotal(items, orderData.couponCode);
  const payment = await processPayment(total, orderData.paymentMethod);
  const order = await createOrder(orderData.userId, items, total, payment.id);

  await updateInventory(items);
  await sendOrderConfirmation(order);

  return order;
}

5.3 DRY, KISS, YAGNI

DRY (Don't Repeat Yourself)

typescript
// YANLIS: Tekrar eden kod
async function getActiveUsers() {
  const result = await db.query('SELECT * FROM users WHERE status = $1', ['active']);
  return result.rows.map(row => ({
    id: row.id,
    name: row.name,
    email: row.email,
    avatar: row.avatar_url
  }));
}

async function getPremiumUsers() {
  const result = await db.query('SELECT * FROM users WHERE type = $1', ['premium']);
  return result.rows.map(row => ({
    id: row.id,
    name: row.name,
    email: row.email,
    avatar: row.avatar_url  // Ayni mapping tekrar
  }));
}

// DOGRU: Ortak mantigi cikar
function mapUserRow(row: any): UserDTO {
  return {
    id: row.id,
    name: row.name,
    email: row.email,
    avatar: row.avatar_url
  };
}

async function getActiveUsers() {
  const result = await db.query('SELECT * FROM users WHERE status = $1', ['active']);
  return result.rows.map(mapUserRow);
}

async function getPremiumUsers() {
  const result = await db.query('SELECT * FROM users WHERE type = $1', ['premium']);
  return result.rows.map(mapUserRow);
}

KISS (Keep It Simple, Stupid)

typescript
// YANLIS: Asiri muhendislik
class StringReverser {
  private strategy: IReverseStrategy;

  constructor(strategy: IReverseStrategy) {
    this.strategy = strategy;
  }

  reverse(input: string): string {
    return this.strategy.execute(input);
  }
}

// DOGRU: Basit tut
function reverseString(input: string): string {
  return input.split('').reverse().join('');
}

YAGNI (You Ain't Gonna Need It)

typescript
// YANLIS: Ihtiyac olmayan ozellikler ekleme
class UserService {
  async getUser(id: string) { /* ... */ }
  async createUser(data: UserData) { /* ... */ }
  async updateUser(id: string, data: UserData) { /* ... */ }
  async deleteUser(id: string) { /* ... */ }

  // Bunlar henuz kimse istemedi!
  async exportUsersToXML() { /* ... */ }
  async importUsersFromCSV() { /* ... */ }
  async generateUserHeatmap() { /* ... */ }
  async predictUserChurn() { /* ... */ } // Yapay zeka?!
}

// DOGRU: Sadece ihtiyac olani yaz
class UserService {
  async getUser(id: string) { /* ... */ }
  async createUser(data: UserData) { /* ... */ }
  async updateUser(id: string, data: UserData) { /* ... */ }
  async deleteUser(id: string) { /* ... */ }
  // Yeni ozellik gerektiginde ekle, onceden degil
}

5.4 Boy Scout Rule

"Bir yeri buldugundan daha temiz birak."

Her dosyaya dokundugununda küçük bir iyilestirme yap:

  • Kullanilmayan import'u sil
  • Magic number'i constant yap
  • Değişken ismini duzelt
  • Gereksiz yorumu sil

5.5 Comments (Yorum Satirlari)

typescript
// YANLIS: Kodu aciklayan yorumlar (kod kendini aciklamali)
// Kullaniciyi ID'ye gore bul
function getUser(id: string) { /* ... */ }

// Fiyati %15 indir
const discountedPrice = price * 0.85;

// DOGRU: "Neden" aciklayan yorumlar
// Stripe API 3 saniye timeout sonrasinda retry yapiyor,
// bu yuzden kendi retry mekanizmamizi devre disi birakiyoruz
const stripeConfig = { maxRetries: 0 };

// Turk vergi mevzuatina gore %18 KDV eklenmeli (VUK 229. madde)
const taxRate = 0.18;

// TODO: Bu gecici cozum, OAuth2 entegrasyonu tamamlaninca kaldirilacak
// Ref: JIRA-1234
const hardcodedToken = process.env.TEMP_API_TOKEN;

Yorum Ne Zaman Yaz:

  • Is kurali aciklamasi (neden bu hesaplama?)
  • Hack/workaround aciklamasi
  • TODO / FIXME
  • Public API/library dokumantasyonu
  • Regex pattern aciklamasi

Yorum Ne Zaman Yazma:

  • Kodu Turkce/Ingilizce'ye cevirme ("kullaniciyi sil")
  • Git blame ile gorulecek degisiklik notlari
  • Yorum satira alinmis (commented-out) kod -- sil, git'ten geri getirebilirsin

5.6 Magic Numbers ve Constants

typescript
// YANLIS: Magic number'lar
if (password.length < 8) { /* ... */ }
if (retryCount > 3) { /* ... */ }
if (user.role === 2) { /* ... */ }
setTimeout(callback, 86400000);

// DOGRU: Constants
const MIN_PASSWORD_LENGTH = 8;
const MAX_RETRY_COUNT = 3;
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;

enum UserRole {
  User = 1,
  Admin = 2,
  SuperAdmin = 3
}

if (password.length < MIN_PASSWORD_LENGTH) { /* ... */ }
if (retryCount > MAX_RETRY_COUNT) { /* ... */ }
if (user.role === UserRole.Admin) { /* ... */ }
setTimeout(callback, TWENTY_FOUR_HOURS_MS);

5.7 Early Return / Guard Clauses

typescript
// YANLIS: Derin ic ice kosullar (pyramid of doom)
function processPayment(user: User, order: Order, payment: Payment) {
  if (user) {
    if (user.isActive) {
      if (order) {
        if (order.status === 'pending') {
          if (payment.amount > 0) {
            if (payment.method === 'credit_card') {
              // Gercek is mantigi burada, 6 seviye icerde
              return chargeCard(payment);
            } else {
              throw new Error('Invalid payment method');
            }
          } else {
            throw new Error('Invalid amount');
          }
        } else {
          throw new Error('Order not pending');
        }
      } else {
        throw new Error('Order not found');
      }
    } else {
      throw new Error('User not active');
    }
  } else {
    throw new Error('User not found');
  }
}

// DOGRU: Guard clauses ile erken cikis
function processPayment(user: User, order: Order, payment: Payment) {
  if (!user) throw new Error('User not found');
  if (!user.isActive) throw new Error('User not active');
  if (!order) throw new Error('Order not found');
  if (order.status !== 'pending') throw new Error('Order not pending');
  if (payment.amount <= 0) throw new Error('Invalid amount');
  if (payment.method !== 'credit_card') throw new Error('Invalid payment method');

  // Gercek is mantigi -- duz, okunabilir
  return chargeCard(payment);
}

5.8 Code Smells Tablosu

Code SmellBelirtisiÇözüm
Long Method50+ satirlik fonksiyonExtract Method -- küçük fonksiyonlara bol
Long Parameter List5+ parametreParameter Object / DTO kullan
Duplicate CodeAyni mantik 2+ yerdeOrtak fonksiyon/sınıf cikar
Feature EnvySınıf baska sinifin verisini cok kullanirMetodu verinin oldugu sinifa tasi
Data ClumpsAyni değişken grubu hep birlikte gezerSınıf/interface oluştur
Primitive ObsessionHer sey string/number, ozel tip yokValue Object oluştur (Money, Email, PhoneNumber)
Switch StatementsBüyük switch/if-else zincirleriStrategy/State pattern veya polymorphism
Speculative GeneralityKullanilmayan soyutlamalarSil, YAGNI
Dead CodeKullanilmayan kod bloklariSil
CommentsCok fazla yorum, kodun kendisi anlasilmiyorKodu refactor et, kendini aciklayan hale getir
God Class1000+ satirlik sınıfSorumluluga gore bol
Shotgun SurgeryBir degisiklik 10+ dosyayi etkilerIliskili kodu bir araya getir

6) API Tasarimi (REST Best Practices)

6.1 RESTful URL Yapisi

# Kaynak (resource) isimleri cogul ve kucuk harf
GET    /api/v1/users              # Tum kullanicilari listele
GET    /api/v1/users/123          # Tek kullanici getir
POST   /api/v1/users              # Yeni kullanici olustur
PUT    /api/v1/users/123          # Kullaniciyi tamamen guncelle
PATCH  /api/v1/users/123          # Kullaniciyi kismi guncelle
DELETE /api/v1/users/123          # Kullaniciyi sil

# Iliskili kaynaklar (nested resources)
GET    /api/v1/users/123/orders          # Kullanicinin siparisleri
GET    /api/v1/users/123/orders/456      # Kullanicinin belirli siparisi
POST   /api/v1/users/123/addresses       # Kullaniciya adres ekle

# Aksiyon gerektiren islemler (fiil URL'de olabilir -- istisna)
POST   /api/v1/orders/123/cancel         # Siparisi iptal et
POST   /api/v1/auth/login                # Giris yap
POST   /api/v1/auth/logout               # Cikis yap
POST   /api/v1/reports/generate          # Rapor olustur

# YANLIS URL ornekleri
GET    /api/v1/getUsers             # Fiil kullanma (GET zaten "getir" demek)
GET    /api/v1/user                 # Tekil degil, cogul kullan
POST   /api/v1/users/create         # POST zaten "olustur" demek
DELETE /api/v1/users/delete/123     # DELETE metodu yeterli
GET    /api/v1/Users                # Buyuk harf kullanma

6.2 HTTP Methods Tablosu

MethodAmacIdempotentSafeBodyÖrnek
GETKaynak getirEvetEvetYokGET /api/users/123
POSTYeni kaynak oluşturHayirHayirVarPOST /api/users
PUTKaynagi tamamen değiştirEvetHayirVarPUT /api/users/123
PATCHKaynagi kismi değiştirEvetHayirVarPATCH /api/users/123
DELETEKaynagi silEvetHayirOpsiyonelDELETE /api/users/123
HEADSadece header (body yok)EvetEvetYokHEAD /api/users/123
OPTIONSDesteklenen metotlari sorEvetEvetYokOPTIONS /api/users

Idempotent: Ayni istegi 10 kez gondersen ayni sonucu alirsin. POST haric hepsi idempotent. Safe: Sunucu durumunu degistirmez. Sadece GET, HEAD, OPTIONS safe'tir.

6.3 HTTP Status Codes Tablosu

2xx -- Basarili

KodIsimNe Zaman Kullan
200OKGenel basari (GET, PUT, PATCH, DELETE)
201CreatedYeni kaynak olusturuldu (POST)
204No ContentBasarili ama donecek veri yok (DELETE)

3xx -- Yonlendirme

KodIsimNe Zaman Kullan
301Moved PermanentlyKaynak kalici olarak tasindi
302FoundGecici yonlendirme
304Not ModifiedCache hala gecerli (ETag/If-None-Match)

4xx -- Istemci Hatasi

KodIsimNe Zaman Kullan
400Bad RequestGecersiz istek (validation hatasi, yanlis JSON)
401UnauthorizedKimlik dogrulanmadi (token yok/gecersiz)
403ForbiddenKimlik dogrulandi ama yetki yok
404Not FoundKaynak bulunamadi
405Method Not AllowedBu endpoint bu HTTP method'u desteklemiyor
409ConflictCakisma (ornegin: ayni email ile kayit var)
413Payload Too LargeIstek gövdesi cok büyük
415Unsupported Media TypeDesteklenmeyen Content-Type
422Unprocessable EntitySozdizimi doğru ama anlam olarak gecersiz
429Too Many RequestsRate limit asildi

5xx -- Sunucu Hatasi

KodIsimNe Zaman Kullan
500Internal Server ErrorBeklenmeyen sunucu hatasi
502Bad GatewayUpstream servis cevap vermedi
503Service UnavailableSunucu gecici olarak kullanilamaz (bakim, asiri yuk)
504Gateway TimeoutUpstream servis zaman asimina ugradi

6.4 Response Envelope

Tutarli bir response yapisi kullanimda büyük kolaylik sağlar:

typescript
// Standart response yapisi
interface ApiResponse<T> {
  success: boolean;
  data: T | null;
  message: string | null;
  errors: ValidationError[] | null;
  meta: MetaData | null;
}

interface ValidationError {
  field: string;
  message: string;
  code: string;
}

interface MetaData {
  total?: number;
  page?: number;
  limit?: number;
  totalPages?: number;
  timestamp?: string;
  requestId?: string;
}
typescript
// Basarili liste response
{
  "success": true,
  "data": [
    { "id": 1, "name": "iPhone 15", "price": 49999.99 },
    { "id": 2, "name": "MacBook Pro", "price": 89999.99 }
  ],
  "message": null,
  "errors": null,
  "meta": {
    "total": 156,
    "page": 1,
    "limit": 20,
    "totalPages": 8,
    "timestamp": "2025-01-15T10:30:00Z",
    "requestId": "req-abc123"
  }
}

// Basarili tekil response
{
  "success": true,
  "data": {
    "id": 1,
    "name": "iPhone 15",
    "price": 49999.99,
    "stock": 42
  },
  "message": null,
  "errors": null,
  "meta": null
}

// Hata response (validation)
{
  "success": false,
  "data": null,
  "message": "Validation failed",
  "errors": [
    { "field": "email", "message": "Gecerli bir email adresi giriniz", "code": "INVALID_EMAIL" },
    { "field": "password", "message": "En az 8 karakter olmali", "code": "MIN_LENGTH" }
  ],
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "requestId": "req-def456"
  }
}

// Hata response (genel)
{
  "success": false,
  "data": null,
  "message": "Product not found",
  "errors": null,
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "requestId": "req-ghi789"
  }
}

Response Helper:

typescript
// utils/response.ts
class ApiResponseHelper {
  static success<T>(data: T, message?: string, meta?: MetaData): ApiResponse<T> {
    return {
      success: true,
      data,
      message: message || null,
      errors: null,
      meta: meta || null
    };
  }

  static paginated<T>(data: T[], total: number, page: number, limit: number): ApiResponse<T[]> {
    return {
      success: true,
      data,
      message: null,
      errors: null,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit)
      }
    };
  }

  static error(message: string, errors?: ValidationError[], statusCode?: number): ApiResponse<null> {
    return {
      success: false,
      data: null,
      message,
      errors: errors || null,
      meta: {
        timestamp: new Date().toISOString()
      }
    };
  }
}

// Kullanim
res.status(200).json(ApiResponseHelper.success(product));
res.status(200).json(ApiResponseHelper.paginated(products, total, page, limit));
res.status(400).json(ApiResponseHelper.error('Validation failed', validationErrors));

6.5 Pagination (Sayfalama)

Offset-Based Pagination

GET /api/products?page=3&limit=20

SELECT * FROM products ORDER BY created_at DESC LIMIT 20 OFFSET 40;

Cursor-Based Pagination

GET /api/products?cursor=eyJpZCI6MTAwfQ&limit=20

-- cursor = base64 encoded { "id": 100 }
SELECT * FROM products WHERE id < 100 ORDER BY id DESC LIMIT 20;

Karşılaştırma Tablosu:

ÖzellikOffset-BasedCursor-Based
BasitlikBasit, anlasilirDaha karmasik
Sayfa atlamaEvet (page=5 gibi)Hayir (sadece ileri/geri)
Performans (büyük veri)Yavas (OFFSET 100000 agir)Hızlı (index kullanir)
TutarlilikVeri degisirse kayma olurTutarli sonuclar
SEOURL'de sayfa numarasiUygun degil
Kullanım alaniAdmin paneller, web siteleriMobil uygulamalar, feed'ler, real-time
typescript
// Cursor-based pagination implementasyonu
interface CursorPaginationResult<T> {
  items: T[];
  nextCursor: string | null;
  hasMore: boolean;
}

async function getProductsWithCursor(
  cursor: string | null,
  limit: number = 20
): Promise<CursorPaginationResult<Product>> {
  let query = 'SELECT * FROM products WHERE is_active = true';
  const params: any[] = [];

  if (cursor) {
    const decoded = JSON.parse(Buffer.from(cursor, 'base64').toString());
    params.push(decoded.id);
    query += ` AND id < $${params.length}`;
  }

  params.push(limit + 1); // 1 fazla cek, hasMore belirlemek icin
  query += ` ORDER BY id DESC LIMIT $${params.length}`;

  const result = await db.query(query, params);
  const items = result.rows.slice(0, limit);
  const hasMore = result.rows.length > limit;

  const nextCursor = hasMore
    ? Buffer.from(JSON.stringify({ id: items[items.length - 1].id })).toString('base64')
    : null;

  return { items, nextCursor, hasMore };
}

6.6 Filtering, Sorting, Searching

# Filtreleme
GET /api/products?category=electronics&minPrice=100&maxPrice=500&inStock=true

# Siralama
GET /api/products?sortBy=price&sortOrder=asc
GET /api/products?sort=-price,+name    # alternatif: - desc, + asc

# Arama
GET /api/products?search=iphone&searchFields=name,description

# Hepsi birlikte
GET /api/products?category=electronics&minPrice=100&sortBy=price&sortOrder=asc&search=samsung&page=1&limit=20
typescript
// Genel amacli query builder
function buildProductQuery(filters: ProductFilters) {
  const conditions: string[] = ['is_active = true'];
  const params: any[] = [];

  if (filters.category) {
    params.push(filters.category);
    conditions.push(`category_id = $${params.length}`);
  }

  if (filters.minPrice !== undefined) {
    params.push(filters.minPrice);
    conditions.push(`price >= $${params.length}`);
  }

  if (filters.maxPrice !== undefined) {
    params.push(filters.maxPrice);
    conditions.push(`price <= $${params.length}`);
  }

  if (filters.inStock) {
    conditions.push('stock > 0');
  }

  if (filters.search) {
    params.push(`%${filters.search}%`);
    conditions.push(`(name ILIKE $${params.length} OR description ILIKE $${params.length})`);
  }

  // Guvenli sort -- whitelist ile SQL injection'i onle
  const allowedSortFields = ['name', 'price', 'created_at', 'stock'];
  const sortBy = allowedSortFields.includes(filters.sortBy || '')
    ? filters.sortBy
    : 'created_at';
  const sortOrder = filters.sortOrder === 'asc' ? 'ASC' : 'DESC';

  return {
    where: conditions.join(' AND '),
    params,
    orderBy: `${sortBy} ${sortOrder}`
  };
}

6.7 API Versiyonlama

YöntemÖrnekAvantajDezavantaj
URL Path/api/v1/users /api/v2/usersBasit, acik, cache dostuURL kirliligi
HeaderAccept: application/vnd.api.v2+jsonTemiz URLGorunurluk düşük, test zor
Query Param/api/users?version=2BasitCache sorunlari, kotu pratik

Öneri: URL path versiyonlama (/api/v1/...) en yaygin ve pratik yontemdir. Cogu büyük API (GitHub, Stripe, Twitter) bu yontemi kullanir.

typescript
// URL path versiyonlama
import { Router } from 'express';

const v1Router = Router();
const v2Router = Router();

// V1: Eski response formati
v1Router.get('/users/:id', async (req, res) => {
  const user = await userService.getUser(req.params.id);
  res.json({
    id: user.id,
    name: user.name,
    email: user.email
  });
});

// V2: Yeni response formati (genisletilmis)
v2Router.get('/users/:id', async (req, res) => {
  const user = await userService.getUser(req.params.id);
  res.json({
    success: true,
    data: {
      id: user.id,
      fullName: user.name,  // alan adi degisti
      email: user.email,
      profile: {
        avatar: user.avatar,
        bio: user.bio
      }
    }
  });
});

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

6.8 Rate Limiting

typescript
// Rate limiting middleware (basit in-memory)
const rateLimits = new Map<string, { count: number; resetAt: number }>();

function rateLimiter(options: { windowMs: number; max: number }) {
  return (req: Request, res: Response, next: NextFunction) => {
    const key = req.ip || 'unknown';
    const now = Date.now();
    const record = rateLimits.get(key);

    if (!record || record.resetAt <= now) {
      rateLimits.set(key, { count: 1, resetAt: now + options.windowMs });
      setRateLimitHeaders(res, options.max, options.max - 1, now + options.windowMs);
      return next();
    }

    if (record.count >= options.max) {
      setRateLimitHeaders(res, options.max, 0, record.resetAt);
      return res.status(429).json({
        success: false,
        message: 'Too many requests. Please try again later.',
        retryAfter: Math.ceil((record.resetAt - now) / 1000)
      });
    }

    record.count++;
    setRateLimitHeaders(res, options.max, options.max - record.count, record.resetAt);
    next();
  };
}

function setRateLimitHeaders(res: Response, limit: number, remaining: number, resetAt: number) {
  res.set({
    'X-RateLimit-Limit': limit.toString(),
    'X-RateLimit-Remaining': remaining.toString(),
    'X-RateLimit-Reset': Math.ceil(resetAt / 1000).toString()
  });
}

// Kullanim
app.use('/api/', rateLimiter({ windowMs: 15 * 60 * 1000, max: 100 })); // 15 dk'da 100 istek
app.use('/api/auth/login', rateLimiter({ windowMs: 15 * 60 * 1000, max: 5 })); // Login: 15 dk'da 5
typescript
// Production: express-rate-limit + Redis store
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';

const redisClient = createClient({ url: process.env.REDIS_URL });

const apiLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args) => redisClient.sendCommand(args) }),
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  message: { success: false, message: 'Too many requests' }
});

app.use('/api/', apiLimiter);

6.9 GraphQL vs REST Karşılaştırma

ÖzellikRESTGraphQL
Veri getirmeSabit response, over/under-fetching olabilirIstemci istedigi alanlari secer
Endpoint sayisiKaynak basina endpointTek endpoint (/graphql)
VersiyonlamaURL veya header ileGenellikle gereksiz (schema evolution)
CacheHTTP cache (ETag, CDN) kolayHTTP cache zor, ozel cache gerekli
Dosya uploadDogrudan desteklerEk konfigürasyon gerekli
Öğrenme egrisiDüşükOrta-Yüksek
Over-fetchingVar (tüm alanlar gelir)Yok (sadece istenen alanlar)
Under-fetchingVar (N+1 request)Yok (tek request'te iliskili veri)
ToolingPostman, curl, her seyApollo, Relay, ozel tooling
Real-timeWebSocket, SSE ayriSubscription built-in
Dosya boyutuKüçükQuery buyuyebilir
GüvenlikStandartQuery depth limiting, complexity analysis
Ne zaman seçCRUD agirlikli, public API, basitlikKarmasik iliskili veri, mobil, dashboard

7) Authentication & Authorization

7.1 Session vs JWT Karşılaştırma

ÖzellikSession-BasedJWT (Token-Based)
Durum (state)Stateful (sunucuda saklanir)Stateless (token icinde)
OlceklemeSticky session veya paylasilmis store gerekirKolayca olceklenir (state yok)
Guvenligi bosaltmaSunucudan session silToken expire olana kadar gecerli (revocation zor)
PerformansHer istekte session store'a gitToken'i doğrula (CPU islemi, DB yok)
Cross-domainZor (cookie same-origin)Kolay (Authorization header)
Mobil uyumlulukCookie yönetimi karmasikToken gonderme basit
CSRF riskiVar (cookie otomatik gider)Yok (header ile gonderilir)
XSS riskiDüşük (httpOnly cookie)Yüksek (localStorage'da saklanirsa)
BoyutKüçük session IDBüyük (payload icerigi)
Ideal kullanımGeleneksel web uygulamalari (SSR)SPA, mobil, microservices, API

7.2 JWT Yapisi

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDAwMzYwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
│                                       │                                                                                              │
│         Header (Base64)               │                           Payload (Base64)                                                   │  Signature
│                                       │                                                                                              │
│  {                                    │  {                                                                                           │  HMACSHA256(
│    "alg": "HS256",                    │    "userId": "123",                                                                          │    base64(header) + "." +
│    "typ": "JWT"                       │    "role": "admin",                                                                          │    base64(payload),
│  }                                    │    "iat": 1700000000,    // issued at                                                        │    secret
│                                       │    "exp": 1700003600     // expires (1 saat)                                                 │  )
│                                       │  }                                                                                           │
typescript
// JWT olusturma ve dogrulama
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET!;
const ACCESS_TOKEN_EXPIRY = '15m';   // 15 dakika
const REFRESH_TOKEN_EXPIRY = '7d';   // 7 gun

// Token olusturma
function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { userId: user.id, role: user.role, email: user.email },
    JWT_SECRET,
    { expiresIn: ACCESS_TOKEN_EXPIRY }
  );

  const refreshToken = jwt.sign(
    { userId: user.id, tokenType: 'refresh' },
    JWT_SECRET,
    { expiresIn: REFRESH_TOKEN_EXPIRY }
  );

  return { accessToken, refreshToken };
}

// Token dogrulama middleware
function authMiddleware(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ success: false, message: 'No token provided' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload;
    req.user = { userId: decoded.userId, role: decoded.role };
    next();
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ success: false, message: 'Token expired' });
    }
    return res.status(401).json({ success: false, message: 'Invalid token' });
  }
}

7.3 Access + Refresh Token Akisi

┌──────────┐                              ┌──────────┐
│  Client  │                              │  Server  │
└────┬─────┘                              └────┬─────┘
     │                                         │
     │  1. POST /auth/login                    │
     │  { email, password }                    │
     │────────────────────────────────────────►│
     │                                         │  Dogrula, token olustur
     │  2. Response:                           │
     │  { accessToken (15m), refreshToken (7d)}│
     │◄────────────────────────────────────────│
     │                                         │
     │  3. GET /api/profile                    │
     │  Authorization: Bearer <accessToken>    │
     │────────────────────────────────────────►│
     │                                         │  Token dogrula
     │  4. Response: { user data }             │
     │◄────────────────────────────────────────│
     │                                         │
     │  ... 15 dakika sonra ...                │
     │                                         │
     │  5. GET /api/profile                    │
     │  Authorization: Bearer <expiredToken>   │
     │────────────────────────────────────────►│
     │                                         │  Token suresi doldu
     │  6. 401 Unauthorized                    │
     │◄────────────────────────────────────────│
     │                                         │
     │  7. POST /auth/refresh                  │
     │  { refreshToken }                       │
     │────────────────────────────────────────►│
     │                                         │  Refresh token dogrula
     │  8. Response:                           │  Yeni token cifti olustur
     │  { newAccessToken, newRefreshToken }    │
     │◄────────────────────────────────────────│
     │                                         │
typescript
// Refresh token endpoint
app.post('/auth/refresh', async (req, res) => {
  const { refreshToken } = req.body;

  if (!refreshToken) {
    return res.status(400).json({ success: false, message: 'Refresh token required' });
  }

  try {
    // 1. Token'i dogrula
    const decoded = jwt.verify(refreshToken, JWT_SECRET) as JwtPayload;

    if (decoded.tokenType !== 'refresh') {
      return res.status(401).json({ success: false, message: 'Invalid token type' });
    }

    // 2. Token'in blacklist'te olup olmadigini kontrol et
    const isRevoked = await redisClient.get(`revoked:${refreshToken}`);
    if (isRevoked) {
      return res.status(401).json({ success: false, message: 'Token has been revoked' });
    }

    // 3. Kullaniciyi getir (hala aktif mi?)
    const user = await userRepo.findById(decoded.userId);
    if (!user || !user.isActive) {
      return res.status(401).json({ success: false, message: 'User not found or inactive' });
    }

    // 4. Eski refresh token'i iptal et (token rotation)
    await redisClient.set(`revoked:${refreshToken}`, '1', { EX: 7 * 24 * 60 * 60 });

    // 5. Yeni token cifti olustur
    const tokens = generateTokens(user);

    res.json({ success: true, data: tokens });
  } catch (error) {
    return res.status(401).json({ success: false, message: 'Invalid refresh token' });
  }
});

7.4 OAuth 2.0

Authorization Code Flow (Web uygulamalari için)

┌────────┐     ┌───────────┐     ┌────────────────┐
│  User  │     │  Client   │     │ Auth Provider   │
│        │     │  (App)    │     │ (Google/GitHub)  │
└───┬────┘     └─────┬─────┘     └───────┬────────┘
    │                │                     │
    │  1. Login butonuna tikla             │
    │───────────────►│                     │
    │                │  2. Redirect:       │
    │                │  /authorize?        │
    │                │  client_id=xxx&     │
    │◄───────────────│  redirect_uri=xxx&  │
    │                │  scope=email+profile│
    │  3. Giris yap + izin ver             │
    │─────────────────────────────────────►│
    │                │                     │
    │  4. Redirect: callback?code=abc123   │
    │◄─────────────────────────────────────│
    │───────────────►│                     │
    │                │  5. POST /token     │
    │                │  { code, secret }   │
    │                │────────────────────►│
    │                │                     │
    │                │  6. { access_token }│
    │                │◄────────────────────│
    │                │                     │
    │                │  7. GET /userinfo   │
    │                │  Bearer token       │
    │                │────────────────────►│
    │                │                     │
    │                │  8. { user data }   │
    │                │◄────────────────────│

PKCE (Proof Key for Code Exchange -- SPA / Mobil için)

Authorization Code Flow'un güvenli versiyonu. Client secret kullanilmaz, yerine code_verifier ve code_challenge kullanilir.

typescript
// PKCE Flow -- SPA ornegi
import crypto from 'crypto';

// 1. code_verifier olustur (random 43-128 karakter)
function generateCodeVerifier(): string {
  return crypto.randomBytes(32).toString('base64url');
}

// 2. code_challenge olustur (verifier'in SHA256 hash'i)
function generateCodeChallenge(verifier: string): string {
  return crypto.createHash('sha256').update(verifier).digest('base64url');
}

// 3. Authorization URL
const verifier = generateCodeVerifier();
const challenge = generateCodeChallenge(verifier);

const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
  `client_id=${CLIENT_ID}` +
  `&redirect_uri=${REDIRECT_URI}` +
  `&response_type=code` +
  `&scope=openid email profile` +
  `&code_challenge=${challenge}` +
  `&code_challenge_method=S256`;

// 4. Callback'te code ile token al
async function exchangeCodeForToken(code: string) {
  const response = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      client_id: CLIENT_ID,
      code,
      code_verifier: verifier, // Secret yerine verifier gonder
      grant_type: 'authorization_code',
      redirect_uri: REDIRECT_URI
    })
  });

  return response.json(); // { access_token, id_token, refresh_token }
}

Client Credentials (Machine-to-Machine)

typescript
// Client Credentials -- Servisler arasi iletisim
async function getServiceToken() {
  const response = await fetch('https://auth.example.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'client_credentials',
      client_id: process.env.SERVICE_CLIENT_ID,
      client_secret: process.env.SERVICE_CLIENT_SECRET,
      audience: 'https://api.example.com'
    })
  });

  const data = await response.json();
  return data.access_token;
}

// Diger servise istek at
const token = await getServiceToken();
const orders = await fetch('https://order-service.internal/api/orders', {
  headers: { Authorization: `Bearer ${token}` }
});

7.5 RBAC (Role-Based Access Control)

typescript
// Rol ve izin tanimlamalari
const PERMISSIONS = {
  // Urun izinleri
  'product:read':   'Urunleri goruntuleyebilir',
  'product:create': 'Yeni urun olusturabilir',
  'product:update': 'Urun guncelleyebilir',
  'product:delete': 'Urun silebilir',

  // Siparis izinleri
  'order:read':     'Siparisleri goruntuleyebilir',
  'order:create':   'Siparis olusturabilir',
  'order:update':   'Siparis guncelleyebilir',
  'order:cancel':   'Siparis iptal edebilir',

  // Kullanici izinleri
  'user:read':      'Kullanicilari goruntuleyebilir',
  'user:create':    'Kullanici olusturabilir',
  'user:update':    'Kullanici guncelleyebilir',
  'user:delete':    'Kullanici silebilir',

  // Rapor izinleri
  'report:read':    'Raporlari goruntuleyebilir',
  'report:export':  'Raporlari export edebilir'
};

// Roller ve izinleri
const ROLES = {
  customer: [
    'product:read',
    'order:create', 'order:read'
  ],
  seller: [
    'product:read', 'product:create', 'product:update',
    'order:read', 'order:update'
  ],
  admin: [
    'product:read', 'product:create', 'product:update', 'product:delete',
    'order:read', 'order:create', 'order:update', 'order:cancel',
    'user:read', 'user:create', 'user:update',
    'report:read', 'report:export'
  ],
  superadmin: Object.keys(PERMISSIONS) // Tum izinler
};
Rolproduct:readproduct:createproduct:deleteorder:readorder:canceluser:deletereport:export
customer+--+---
seller++-+---
admin+++++-+
superadmin+++++++
typescript
// RBAC middleware
function requirePermission(...requiredPermissions: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userRole = req.user?.role;
    if (!userRole) {
      return res.status(401).json({ success: false, message: 'Unauthorized' });
    }

    const userPermissions = ROLES[userRole as keyof typeof ROLES] || [];
    const hasAll = requiredPermissions.every(p => userPermissions.includes(p));

    if (!hasAll) {
      return res.status(403).json({
        success: false,
        message: 'Insufficient permissions',
        required: requiredPermissions
      });
    }

    next();
  };
}

// Kullanim
router.get('/products', requirePermission('product:read'), listProducts);
router.post('/products', requirePermission('product:create'), createProduct);
router.delete('/products/:id', requirePermission('product:delete'), deleteProduct);
router.get('/reports', requirePermission('report:read', 'report:export'), getReports);

7.6 Token Saklama

YöntemXSS RiskiCSRF RiskiErisilebilirlikÖneri
localStorageYUKSEK (JS ile okunabilir)YokKolayProduction'da kullanma
sessionStorageYUKSEK (JS ile okunabilir)YokSayfa kapatinca silinirProduction'da kullanma
httpOnly CookieDUSUK (JS ile okunamaz)VAR (CSRF token ile coz)Otomatik gonderilirEn güvenli -- ONERILEN
Memory (değişken)OrtaYokSayfa yenileyince kaybederSPA için gecici çözüm
typescript
// httpOnly cookie ile token saklama (en guvenli)
app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await authService.login(email, password);
  const { accessToken, refreshToken } = generateTokens(user);

  // Access token: httpOnly cookie
  res.cookie('access_token', accessToken, {
    httpOnly: true,       // JavaScript ile okunamaz
    secure: true,         // Sadece HTTPS uzerinden gonderilir
    sameSite: 'strict',   // CSRF korunma
    maxAge: 15 * 60 * 1000, // 15 dakika
    path: '/'
  });

  // Refresh token: httpOnly cookie
  res.cookie('refresh_token', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 gun
    path: '/auth/refresh'             // Sadece refresh endpoint'ine gonderilir
  });

  res.json({
    success: true,
    data: { user: { id: user.id, name: user.name, email: user.email } }
  });
});

// Cookie'den token oku middleware
function cookieAuthMiddleware(req: Request, res: Response, next: NextFunction) {
  const token = req.cookies['access_token'];
  if (!token) {
    return res.status(401).json({ success: false, message: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload;
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ success: false, message: 'Invalid token' });
  }
}

8) Database Patterns

8.1 Migration Stratejisi

Migration, veritabani semasini kod olarak versiyonlama sistemidir. Her degisiklik bir migration dosyasi olarak saklanir.

typescript
// Migration dosyasi ornegi (knex.js)
// migrations/20250115_001_create_users_table.ts
import { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
  await knex.schema.createTable('users', (table) => {
    table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
    table.string('name', 100).notNullable();
    table.string('email', 255).notNullable().unique();
    table.string('password_hash', 255).notNullable();
    table.enum('role', ['user', 'admin', 'superadmin']).defaultTo('user');
    table.boolean('is_active').defaultTo(true);
    table.timestamp('email_verified_at').nullable();
    table.timestamp('created_at').defaultTo(knex.fn.now());
    table.timestamp('updated_at').defaultTo(knex.fn.now());

    // Indexler
    table.index('email');
    table.index('role');
    table.index('is_active');
  });
}

export async function down(knex: Knex): Promise<void> {
  await knex.schema.dropTableIfExists('users');
}

Migration Kurallari:

  • Her migration dosyasi timestamp ile isimlendirilmeli (siralama için)
  • Her migration için up (uygula) ve down (geri al) yazmali
  • Production'da uygulanan migration asla degistirilmemeli -- yeni migration yaz
  • Migration'lar küçük ve atomik olmali (bir migration = bir degisiklik)
  • Veri migration'lari (data migration) şema migration'larindan ayrilmali

8.2 Soft Delete

Kaydi veritabanindan silmek yerine "silindi" olarak isaretleme. Geri alma, audit log ve veri butunlugu için kullanilir.

typescript
// Soft delete implementasyonu
// Migration
export async function up(knex: Knex): Promise<void> {
  await knex.schema.alterTable('products', (table) => {
    table.timestamp('deleted_at').nullable();
    table.index('deleted_at');
  });
}

// Repository
class ProductRepository {
  // Tum sorgulara deleted_at IS NULL ekle
  async findAll(): Promise<Product[]> {
    return db.query('SELECT * FROM products WHERE deleted_at IS NULL ORDER BY created_at DESC');
  }

  async findById(id: string): Promise<Product | null> {
    const result = await db.query(
      'SELECT * FROM products WHERE id = $1 AND deleted_at IS NULL',
      [id]
    );
    return result.rows[0] || null;
  }

  // Soft delete: deleted_at set et
  async softDelete(id: string): Promise<void> {
    await db.query(
      'UPDATE products SET deleted_at = NOW() WHERE id = $1',
      [id]
    );
  }

  // Geri yukle
  async restore(id: string): Promise<void> {
    await db.query(
      'UPDATE products SET deleted_at = NULL WHERE id = $1',
      [id]
    );
  }

  // Gercekten sil (admin, GDPR talebi icin)
  async hardDelete(id: string): Promise<void> {
    await db.query('DELETE FROM products WHERE id = $1', [id]);
  }

  // Silinen kayitlari dahil et (admin icin)
  async findAllWithDeleted(): Promise<Product[]> {
    return db.query('SELECT * FROM products ORDER BY created_at DESC');
  }
}

8.3 Audit Log (Degisiklik Takibi)

Kim, ne zaman, neyi degistirdi -- tüm degisiklikleri izleme.

sql
-- Audit log tablosu
CREATE TABLE audit_logs (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  table_name  VARCHAR(100) NOT NULL,
  record_id   VARCHAR(100) NOT NULL,
  action      VARCHAR(20) NOT NULL, -- INSERT, UPDATE, DELETE
  old_values  JSONB,
  new_values  JSONB,
  changed_by  UUID REFERENCES users(id),
  ip_address  INET,
  user_agent  TEXT,
  created_at  TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_audit_table_record ON audit_logs(table_name, record_id);
CREATE INDEX idx_audit_changed_by ON audit_logs(changed_by);
CREATE INDEX idx_audit_created_at ON audit_logs(created_at);
typescript
// Audit log service
class AuditLogService {
  async log(params: {
    tableName: string;
    recordId: string;
    action: 'INSERT' | 'UPDATE' | 'DELETE';
    oldValues?: Record<string, any>;
    newValues?: Record<string, any>;
    changedBy: string;
    ipAddress?: string;
    userAgent?: string;
  }): Promise<void> {
    await db.query(
      `INSERT INTO audit_logs (table_name, record_id, action, old_values, new_values, changed_by, ip_address, user_agent)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
      [
        params.tableName,
        params.recordId,
        params.action,
        params.oldValues ? JSON.stringify(params.oldValues) : null,
        params.newValues ? JSON.stringify(params.newValues) : null,
        params.changedBy,
        params.ipAddress,
        params.userAgent
      ]
    );
  }
}

// Service'te kullanim
class ProductService {
  constructor(
    private productRepo: ProductRepository,
    private auditLog: AuditLogService
  ) {}

  async updateProduct(id: string, data: UpdateProductDTO, userId: string, req: Request) {
    const oldProduct = await this.productRepo.findById(id);
    if (!oldProduct) throw new NotFoundError('Product', id);

    const updatedProduct = await this.productRepo.update(id, data);

    await this.auditLog.log({
      tableName: 'products',
      recordId: id,
      action: 'UPDATE',
      oldValues: { name: oldProduct.name, price: oldProduct.price },
      newValues: { name: updatedProduct.name, price: updatedProduct.price },
      changedBy: userId,
      ipAddress: req.ip,
      userAgent: req.headers['user-agent']
    });

    return updatedProduct;
  }
}

8.4 N+1 Query Problemi

Bir listeyi cekip, her bir eleman için ayri sorgu atma problemi.

typescript
// PROBLEM: N+1 Query
// 1 sorgu: Tum siparisleri cek
const orders = await db.query('SELECT * FROM orders'); // 1 sorgu

// N sorgu: Her siparis icin kullaniciyi cek
for (const order of orders) {
  const user = await db.query(
    'SELECT * FROM users WHERE id = $1',
    [order.user_id]
  ); // N sorgu (100 siparis = 100 sorgu!)
  order.user = user.rows[0];
}
// Toplam: 1 + 100 = 101 sorgu!

// COZUM 1: JOIN kullan
const ordersWithUsers = await db.query(`
  SELECT o.*, u.name as user_name, u.email as user_email
  FROM orders o
  JOIN users u ON u.id = o.user_id
  ORDER BY o.created_at DESC
`); // 1 sorgu

// COZUM 2: Batch loading (IN clause)
const orders = await db.query('SELECT * FROM orders'); // 1 sorgu

const userIds = [...new Set(orders.rows.map(o => o.user_id))];
const users = await db.query(
  'SELECT * FROM users WHERE id = ANY($1)',
  [userIds]
); // 1 sorgu (toplam 2 sorgu!)

const userMap = new Map(users.rows.map(u => [u.id, u]));
orders.rows.forEach(order => {
  order.user = userMap.get(order.user_id);
});

// COZUM 3: ORM'de eager loading (Prisma ornegi)
const orders = await prisma.order.findMany({
  include: {
    user: true,      // JOIN yapar
    items: {
      include: {
        product: true // Nested join
      }
    }
  }
});

8.5 Database Indexing Stratejisi

Index TuruNe ZamanÖrnek
Primary KeyHer tablo (otomatik)id
UniqueTekil olmasi gereken alanlaremail, username
Standard (B-tree)WHERE, ORDER BY, JOIN'da sik kullanilan alanlarstatus, category_id
CompositeBirden fazla alanla sorgulama(user_id, created_at)
PartialBelirli kosuldaki satirlarWHERE is_active = true
GINJSONB, full-text searchtags, metadata
ExpressionFonksiyon sonucu uzerindeLOWER(email)
sql
-- Index ornekleri
-- Sik sorgulanan alanlar
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);

-- Composite index (birden fazla alan)
-- Bu index su sorgulari hizlandirir: WHERE user_id = X AND created_at > Y
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC);

-- Partial index (sadece aktif kayitlar)
-- Silinen kayitlari indexlemeye gerek yok
CREATE INDEX idx_products_active ON products(category_id)
  WHERE deleted_at IS NULL AND is_active = true;

-- GIN index (JSONB arama)
CREATE INDEX idx_products_tags ON products USING GIN(tags);

-- Expression index
CREATE INDEX idx_users_email_lower ON users(LOWER(email));

-- Index kullanim analizi
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = '123' AND status = 'pending';

Indexing Kurallari:

  • Her foreign key için index oluştur
  • WHERE, ORDER BY, GROUP BY'da sik kullanilan alanlara index ekle
  • Cok fazla index yazma islemlerini yavaslatir (INSERT/UPDATE)
  • Kullanilmayan index'leri bul ve sil (pg_stat_user_indexes)
  • Composite index'te siralama önemli: en secici alan solda olmali

8.6 Connection Pooling

typescript
// Connection pool -- veritabani baglantilari havuzu
import { Pool } from 'pg';

const pool = new Pool({
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT || '5432'),
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,

  // Pool ayarlari
  min: 5,              // Minimum acik baglanti
  max: 20,             // Maximum acik baglanti
  idleTimeoutMillis: 30000,     // Bos baglanti 30 sn sonra kapatilir
  connectionTimeoutMillis: 5000, // Baglanti kurma zaman asimi
  maxUses: 7500        // Bir baglanti max 7500 sorgudan sonra yenilenir
});

// Pool olaylari
pool.on('connect', () => {
  console.log('New client connected to pool');
});

pool.on('error', (err) => {
  console.error('Unexpected error on idle client', err);
});

// Graceful shutdown
process.on('SIGTERM', async () => {
  console.log('Closing pool...');
  await pool.end();
  process.exit(0);
});

export default pool;

9) Caching Stratejileri

9.1 Cache Seviyeleri

SeviyeNeredeÖrnekTTLInvalidation
Application CacheUygulama bellegindeNode.js Map, LRU cacheSaniye-dakikaUygulama restart'ta kaybolur
Database Query CacheDB veya RedisSorgu sonucu cache'lemeDakika-saatEvent-based
HTTP CacheBrowser / ProxyETag, Cache-ControlSaat-gunHeader-based
CDN CacheEdge server'larCloudflare, AWS CloudFrontSaat-gunPurge API
OPcode CachePHP runtimeOPcacheUygulama omruConfig/restart

9.2 Cache Patterns

Cache-Aside (Lazy Loading)

En yaygin pattern. Uygulama cache'i kontrol eder, yoksa DB'den okur ve cache'e yazar.

typescript
// Cache-Aside pattern
class ProductService {
  constructor(
    private productRepo: ProductRepository,
    private cache: RedisClient
  ) {}

  async getProduct(id: string): Promise<Product> {
    const cacheKey = `product:${id}`;

    // 1. Cache'e bak
    const cached = await this.cache.get(cacheKey);
    if (cached) {
      return JSON.parse(cached);
    }

    // 2. DB'den oku
    const product = await this.productRepo.findById(id);
    if (!product) throw new NotFoundError('Product', id);

    // 3. Cache'e yaz
    await this.cache.set(cacheKey, JSON.stringify(product), { EX: 3600 }); // 1 saat

    return product;
  }

  async updateProduct(id: string, data: UpdateProductDTO): Promise<Product> {
    const product = await this.productRepo.update(id, data);

    // Cache'i invalidate et
    await this.cache.del(`product:${id}`);
    // Iliskili cache'leri de temizle
    await this.cache.del('product:list:*'); // Pattern ile silme

    return product;
  }
}

Write-Through

Veri hem cache'e hem DB'ye ayni anda yazilir.

typescript
// Write-Through pattern
class ProductService {
  async updateProduct(id: string, data: UpdateProductDTO): Promise<Product> {
    // 1. DB'ye yaz
    const product = await this.productRepo.update(id, data);

    // 2. Cache'e de ayni anda yaz
    await this.cache.set(
      `product:${id}`,
      JSON.stringify(product),
      { EX: 3600 }
    );

    return product;
  }
}

Write-Behind (Write-Back)

Once cache'e yaz, DB'ye asenkron olarak sonra yaz. Performans kazanimi sağlar ama veri kaybi riski vardir.

typescript
// Write-Behind pattern (kavramsal)
class WriteBackCache {
  private writeQueue: Map<string, { data: any; timer: NodeJS.Timeout }> = new Map();

  async write(key: string, data: any): Promise<void> {
    // 1. Hemen cache'e yaz
    await this.cache.set(key, JSON.stringify(data));

    // 2. Mevcut timer varsa iptal et (debounce)
    const existing = this.writeQueue.get(key);
    if (existing) clearTimeout(existing.timer);

    // 3. 5 saniye sonra DB'ye yaz
    const timer = setTimeout(async () => {
      await this.persistToDB(key, data);
      this.writeQueue.delete(key);
    }, 5000);

    this.writeQueue.set(key, { data, timer });
  }

  private async persistToDB(key: string, data: any): Promise<void> {
    // DB'ye kaydet
    await db.query('UPDATE ... SET ... WHERE id = $1', [data.id]);
  }
}

9.3 Redis Temel Kullanım

typescript
import { createClient } from 'redis';

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

// String -- basit key-value
await redis.set('user:123:name', 'Fahri');
await redis.get('user:123:name'); // 'Fahri'
await redis.set('session:abc', JSON.stringify(sessionData), { EX: 3600 }); // 1 saat TTL

// Hash -- nesne alanlari
await redis.hSet('user:123', { name: 'Fahri', email: 'f@example.com', role: 'admin' });
await redis.hGet('user:123', 'name');     // 'Fahri'
await redis.hGetAll('user:123');           // { name: 'Fahri', email: '...', role: 'admin' }

// List -- siralanmis liste (kuyruk icin)
await redis.lPush('notifications:123', JSON.stringify(notification)); // basa ekle
await redis.rPop('notifications:123');   // sondan cik (FIFO)
await redis.lRange('notifications:123', 0, 9); // ilk 10 eleman

// Set -- tekil degerler
await redis.sAdd('online_users', 'user:123');
await redis.sAdd('online_users', 'user:456');
await redis.sMembers('online_users');   // ['user:123', 'user:456']
await redis.sIsMember('online_users', 'user:123'); // true

// TTL (Time To Live)
await redis.set('otp:user123', '485923', { EX: 300 }); // 5 dakika
await redis.ttl('otp:user123'); // kalan saniye

9.4 Cache Invalidation

StratejiAçıklamaAvantajDezavantaj
TTL (Time-Based)Belirli sureden sonra otomatik silinirBasitVeri eski kalabilir
Event-BasedVeri degistiginde cache'i silGuncelEk karmasiklik
Version-BasedKey'e versiyon numarasi ekleAtomikEski versiyon kaynaklari kalir
typescript
// Event-based invalidation
class ProductService {
  constructor(
    private productRepo: ProductRepository,
    private cache: RedisClient,
    private eventBus: EventEmitter
  ) {
    // Product degisikliklerini dinle
    this.eventBus.on('product:updated', (productId) => this.invalidateProduct(productId));
    this.eventBus.on('product:deleted', (productId) => this.invalidateProduct(productId));
  }

  private async invalidateProduct(productId: string): Promise<void> {
    // Iliskili tum cache key'lerini sil
    await Promise.all([
      this.cache.del(`product:${productId}`),
      this.cache.del(`product:${productId}:reviews`),
      this.cache.del(`product:${productId}:recommendations`),
      // List cache'lerini de temizle
      this.invalidateListCaches()
    ]);
  }

  private async invalidateListCaches(): Promise<void> {
    // Pattern ile tum liste cache'lerini sil
    const keys = await this.cache.keys('products:list:*');
    if (keys.length > 0) {
      await this.cache.del(keys);
    }
  }
}

9.5 HTTP Caching

typescript
// ETag ile cache
import crypto from 'crypto';

app.get('/api/products/:id', async (req, res) => {
  const product = await productService.getProduct(req.params.id);
  const productJson = JSON.stringify(product);

  // ETag olustur
  const etag = crypto.createHash('md5').update(productJson).digest('hex');

  // Client'in gonderdigi ETag ile karsilastir
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end(); // Not Modified -- body gonderme
  }

  res.set({
    'ETag': etag,
    'Cache-Control': 'private, max-age=60',  // 1 dakika browser cache
    'Vary': 'Authorization'                   // Kullaniciya ozel cache
  });

  res.json({ success: true, data: product });
});

// Statik kaynaklar icin agresif cache
app.use('/static', express.static('public', {
  maxAge: '1y',                    // 1 yil
  immutable: true,                 // Degismeyecek (hash'li dosyalar)
  etag: true
}));

// API response'lari icin cache header'lari
app.use('/api', (req, res, next) => {
  // Genel API: cache'leme
  res.set('Cache-Control', 'no-store'); // Varsayilan: cache'leme
  next();
});

10) Message Queues & Event-Driven

10.1 Neden Queue Kullanilir?

Uzun suren islemleri kullanicinin beklemesine gerek kalmadan arka planda islemek için:

SenaryoQueue'suzQueue ile
Kayit sonrasi emailKullanici 3-5 sn beklerAninda cevap, email arka planda gider
Resim boyutlandirmaUpload 10 sn surerUpload 1 sn, işlem arka planda
Rapor oluşturmaRequest timeout olur (30 sn)Rapor hazir olunca bildirim gider
PDF oluşturmaAPI yavaslarAPI hızlı, PDF arka planda olusur
Toplu email gonderme1000 email = 30 dk beklemeAninda cevap, kuyruktan gonderilir

10.2 Job/Worker Pattern

┌──────────┐       ┌───────────┐       ┌──────────┐
│ Producer │──────►│   Queue   │──────►│ Consumer │
│ (API)    │       │ (Redis/   │       │ (Worker) │
│          │       │  RabbitMQ)│       │          │
└──────────┘       └───────────┘       └──────────┘

Ornek:
API endpoint        Redis Queue         Worker process
POST /register ───► "send_welcome_   ──► EmailWorker
                     email" job            - Email gonder
                                           - Template render et
typescript
// Producer -- Job olustur (BullMQ ornegi)
import { Queue } from 'bullmq';

const emailQueue = new Queue('email', {
  connection: { host: 'localhost', port: 6379 }
});

// API'de: Job'i kuyruge ekle
app.post('/api/auth/register', async (req, res) => {
  const user = await userService.register(req.body);

  // Email gonderimi kuyruge ekle (async)
  await emailQueue.add('welcome-email', {
    userId: user.id,
    email: user.email,
    name: user.name
  }, {
    attempts: 3,             // 3 kez dene
    backoff: {
      type: 'exponential',
      delay: 2000             // 2s, 4s, 8s
    },
    removeOnComplete: 100,    // Son 100 basarili job'i tut
    removeOnFail: 500         // Son 500 basarisiz job'i tut
  });

  // Kullaniciya hemen cevap dondur (email arka planda gidecek)
  res.status(201).json({ success: true, data: user });
});

// Consumer -- Job'i isle (ayri process)
import { Worker } from 'bullmq';

const emailWorker = new Worker('email', async (job) => {
  const { userId, email, name } = job.data;

  console.log(`Processing job ${job.id}: Send welcome email to ${email}`);

  switch (job.name) {
    case 'welcome-email':
      await emailService.sendWelcome(email, name);
      break;
    case 'order-confirmation':
      await emailService.sendOrderConfirmation(email, job.data.orderId);
      break;
    case 'password-reset':
      await emailService.sendPasswordReset(email, job.data.resetToken);
      break;
  }

  console.log(`Job ${job.id} completed`);
}, {
  connection: { host: 'localhost', port: 6379 },
  concurrency: 5 // Ayni anda 5 job isle
});

emailWorker.on('completed', (job) => {
  console.log(`Job ${job.id} completed successfully`);
});

emailWorker.on('failed', (job, err) => {
  console.error(`Job ${job?.id} failed: ${err.message}`);
});

10.3 Queue Araçları Karşılaştırma

ÖzellikBullMQ (Redis)RabbitMQAWS SQS
AltyapiRedis gerekliAyri serverManaged (AWS)
ProtokolRedis commandsAMQPHTTP/AWS SDK
Siralama garantisiFIFOFIFO (per queue)FIFO (opsiyonel)
RetryBuilt-in (backoff)Manual ack/nackBuilt-in
Dead letter queueVarVarVar
DashboardBull BoardRabbitMQ ManagementAWS Console
OlceklemeRedis clusterCluster + FederationOtomatik
Gecikme (delay)VarPlugin ileVar
Oncelik (priority)VarVarYok (standard)
Kurulum kolayligiKolay (Redis varsa)OrtaKolay (AWS)
MaliyetRedis maliyetiServer maliyetiKullanım basina
Ideal kullanımNode.js projeleriKarmasik routingAWS ekosistemi

10.4 Dead Letter Queue & Retry

typescript
// Dead Letter Queue (DLQ) -- basarisiz job'lari ayri kuyrukta topla
const mainQueue = new Queue('orders', { connection: redisConfig });
const dlq = new Queue('orders-dlq', { connection: redisConfig });

const orderWorker = new Worker('orders', async (job) => {
  try {
    await processOrder(job.data);
  } catch (error) {
    // Son denemeyse DLQ'ya gonder
    if (job.attemptsMade >= (job.opts.attempts || 3) - 1) {
      await dlq.add('failed-order', {
        originalJobId: job.id,
        data: job.data,
        error: (error as Error).message,
        failedAt: new Date().toISOString(),
        attempts: job.attemptsMade + 1
      });
    }
    throw error; // BullMQ retry mekanizmasini tetikle
  }
}, {
  connection: redisConfig,
  concurrency: 10
});

// Exponential backoff -- her denemede bekleme suresi katlanir
// Deneme 1: 2 saniye bekle
// Deneme 2: 4 saniye bekle
// Deneme 3: 8 saniye bekle
// Deneme 4: 16 saniye bekle
// Deneme 5: DLQ'ya gonder

await mainQueue.add('process-order', orderData, {
  attempts: 5,
  backoff: {
    type: 'exponential',
    delay: 2000 // baslangic: 2 saniye
  }
});
Retry Akisi:

Job basarisiz ──► 2s bekle ──► Tekrar dene (1)

                               basarisiz

                               4s bekle ──► Tekrar dene (2)

                                            basarisiz

                                            8s bekle ──► Tekrar dene (3)

                                                         basarisiz

                                                    Dead Letter Queue'ya gonder
                                                    (manuel inceleme icin)

11) Error Handling & Logging

11.1 Error Handling Stratejisi

Custom Exception Siniflari

typescript
// errors/AppError.ts -- Temel hata sinifi
export class AppError extends Error {
  public readonly statusCode: number;
  public readonly code: string;
  public readonly isOperational: boolean;

  constructor(
    message: string,
    statusCode: number,
    code: string,
    isOperational = true
  ) {
    super(message);
    this.statusCode = statusCode;
    this.code = code;
    this.isOperational = isOperational; // Beklenen hata mi, bug mi?
    Object.setPrototypeOf(this, new.target.prototype);
  }
}

// Ozel hata siniflari
export class NotFoundError extends AppError {
  constructor(resource: string, id: string) {
    super(`${resource} not found with id: ${id}`, 404, 'RESOURCE_NOT_FOUND');
  }
}

export class ValidationError extends AppError {
  public readonly errors: Array<{ field: string; message: string }>;

  constructor(message: string, errors: Array<{ field: string; message: string }> = []) {
    super(message, 422, 'VALIDATION_ERROR');
    this.errors = errors;
  }
}

export class UnauthorizedError extends AppError {
  constructor(message = 'Authentication required') {
    super(message, 401, 'UNAUTHORIZED');
  }
}

export class ForbiddenError extends AppError {
  constructor(message = 'Insufficient permissions') {
    super(message, 403, 'FORBIDDEN');
  }
}

export class ConflictError extends AppError {
  constructor(message: string) {
    super(message, 409, 'CONFLICT');
  }
}

export class RateLimitError extends AppError {
  constructor(message = 'Too many requests') {
    super(message, 429, 'RATE_LIMIT_EXCEEDED');
  }
}

Global Error Handler

typescript
// middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../errors/AppError';
import { logger } from '../utils/logger';

export function globalErrorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  // AppError (beklenen hata)
  if (err instanceof AppError) {
    logger.warn({
      code: err.code,
      message: err.message,
      statusCode: err.statusCode,
      path: req.path,
      method: req.method,
      requestId: req.headers['x-request-id']
    });

    return res.status(err.statusCode).json({
      success: false,
      message: err.message,
      code: err.code,
      errors: (err as any).errors || null,
      meta: {
        timestamp: new Date().toISOString(),
        requestId: req.headers['x-request-id']
      }
    });
  }

  // Beklenmeyen hata (bug)
  logger.error({
    message: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
    body: req.body,
    requestId: req.headers['x-request-id']
  });

  // Production'da hata detayini disari gosterme
  const isProduction = process.env.NODE_ENV === 'production';

  res.status(500).json({
    success: false,
    message: isProduction ? 'Internal server error' : err.message,
    code: 'INTERNAL_ERROR',
    stack: isProduction ? undefined : err.stack,
    meta: {
      timestamp: new Date().toISOString(),
      requestId: req.headers['x-request-id']
    }
  });
}

// app.ts -- En sona ekle
app.use(globalErrorHandler);

11.2 Error Response Standardi

typescript
// Tum hata response'lari bu formatta
interface ErrorResponse {
  success: false;
  message: string;          // Kullaniciya gosterilecek mesaj
  code: string;             // Makine tarafindan okunabilir hata kodu
  errors?: FieldError[];    // Validation hatalari (opsiyonel)
  meta: {
    timestamp: string;
    requestId?: string;
    path?: string;
  };
}

// Ornekler:
// 400 Bad Request
{
  "success": false,
  "message": "Invalid request body",
  "code": "BAD_REQUEST",
  "meta": { "timestamp": "2025-01-15T10:30:00Z" }
}

// 422 Validation Error
{
  "success": false,
  "message": "Validation failed",
  "code": "VALIDATION_ERROR",
  "errors": [
    { "field": "email", "message": "Gecerli bir email giriniz" },
    { "field": "password", "message": "En az 8 karakter olmali" }
  ],
  "meta": { "timestamp": "2025-01-15T10:30:00Z" }
}

// 404 Not Found
{
  "success": false,
  "message": "Product not found with id: abc123",
  "code": "RESOURCE_NOT_FOUND",
  "meta": { "timestamp": "2025-01-15T10:30:00Z" }
}

11.3 Logging Seviyeleri

SeviyeOncelikNe Zaman KullanÖrnek
FATAL0 (en yüksek)Uygulama calismaya devam edemezDB baglantisi tamamen koptu
ERROR1Beklenmeyen hata, işlem basarisizOdeme islemi basarisiz, unhandled exception
WARN2Potansiyel sorun, ama işlem devam ediyorRate limit yaklasti, deprecation kullanimi
INFO3Önemli is olaylariKullanici kaydi, siparis oluşturma, deploy
DEBUG4Geliştirme amacli detaySQL sorgusu, cache hit/miss, request/response
TRACE5 (en düşük)En ince detay, nadiren kullanilirFonksiyon giris/cikis, değişken degerleri

Production'da: INFO ve ustu (WARN, ERROR, FATAL) Development'ta: DEBUG ve ustu

11.4 Structured Logging (JSON)

typescript
// winston ile structured logging
import winston from 'winston';

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: {
    service: 'order-service',
    environment: process.env.NODE_ENV
  },
  transports: [
    // Console (development)
    new winston.transports.Console({
      format: process.env.NODE_ENV === 'development'
        ? winston.format.combine(winston.format.colorize(), winston.format.simple())
        : winston.format.json()
    }),
    // File (production)
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

// Kullanim
logger.info('Order created', {
  orderId: 'ord-123',
  userId: 'usr-456',
  amount: 299.99,
  currency: 'TRY',
  items: 3
});

// Cikti (JSON):
// {
//   "level": "info",
//   "message": "Order created",
//   "orderId": "ord-123",
//   "userId": "usr-456",
//   "amount": 299.99,
//   "currency": "TRY",
//   "items": 3,
//   "service": "order-service",
//   "environment": "production",
//   "timestamp": "2025-01-15T10:30:00.000Z"
// }

Correlation ID (Istek Takibi)

typescript
// Her istege benzersiz ID ata -- tum log'larda izlenebilir
import { v4 as uuidv4 } from 'uuid';
import { AsyncLocalStorage } from 'async_hooks';

const asyncLocalStorage = new AsyncLocalStorage<{ requestId: string }>();

// Middleware: Her istege requestId ata
app.use((req: Request, res: Response, next: NextFunction) => {
  const requestId = req.headers['x-request-id'] as string || uuidv4();
  res.set('x-request-id', requestId);

  asyncLocalStorage.run({ requestId }, () => {
    next();
  });
});

// Logger: requestId'yi otomatik ekle
function getLogger() {
  const store = asyncLocalStorage.getStore();
  return {
    info: (message: string, meta?: object) => {
      logger.info(message, { ...meta, requestId: store?.requestId });
    },
    error: (message: string, meta?: object) => {
      logger.error(message, { ...meta, requestId: store?.requestId });
    }
  };
}

// Kullanim -- requestId her log satirinda otomatik bulunur
app.get('/api/orders/:id', async (req, res) => {
  const log = getLogger();
  log.info('Fetching order', { orderId: req.params.id });
  // Log ciktisi: { message: "Fetching order", orderId: "123", requestId: "req-abc-def" }

  const order = await orderService.getOrder(req.params.id);
  log.info('Order fetched successfully', { orderId: order.id });

  res.json({ success: true, data: order });
});

11.5 Error Tracking (Sentry)

typescript
// Sentry entegrasyonu
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0, // Prod'da %10 trace
  integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
    new Sentry.Integrations.Express({ app })
  ]
});

// Sentry request handler (ilk middleware)
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());

// ... routes ...

// Sentry error handler (son middleware, error handler'dan once)
app.use(Sentry.Handlers.errorHandler({
  shouldHandleError(error) {
    // Sadece 500 hatalarini raporla, 4xx client hatalari degil
    if (error instanceof AppError && error.statusCode < 500) return false;
    return true;
  }
}));

app.use(globalErrorHandler);

// Manuel hata raporlama
try {
  await riskyOperation();
} catch (error) {
  Sentry.captureException(error, {
    tags: { module: 'payment', provider: 'stripe' },
    extra: { orderId: '123', amount: 299.99 },
    user: { id: userId, email: userEmail }
  });
  throw error;
}

12) CI/CD Pipeline

12.1 Pipeline Asamalari

┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│  Lint   │───►│  Test   │───►│  Build  │───►│ Deploy  │───►│ Verify  │
│         │    │         │    │         │    │ (Staging)│    │         │
│ ESLint  │    │ Unit    │    │ Docker  │    │         │    │ Smoke   │
│ Prettier│    │ Integ.  │    │ Bundle  │    │ CD tool │    │ Health  │
│ TypeCheck│   │ E2E     │    │ Compile │    │ k8s/ECS │    │ Monitor │
└─────────┘    └─────────┘    └─────────┘    └─────────┘    └─────────┘
     │              │              │              │              │
     ▼              ▼              ▼              ▼              ▼
  Fail?          Fail?          Fail?        Staging OK?     Health OK?
  Stop!          Stop!          Stop!         ──────►         ──────►
                                             Production      Done!

12.2 GitHub Actions Workflow Ornegi

yaml
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '20'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # 1. Lint & Type Check
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Type check
        run: npm run typecheck

  # 2. Test
  test:
    runs-on: ubuntu-latest
    needs: lint
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run migrations
        run: npm run db:migrate
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb

      - name: Run unit tests
        run: npm run test:unit -- --coverage

      - name: Run integration tests
        run: npm run test:integration
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  # 3. Build
  build:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Login to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

  # 4. Deploy to Staging
  deploy-staging:
    runs-on: ubuntu-latest
    needs: build
    environment: staging
    steps:
      - name: Deploy to staging
        run: |
          echo "Deploying ${{ github.sha }} to staging..."
          # kubectl set image deployment/app app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Smoke test
        run: |
          sleep 30
          curl -f https://staging.example.com/health || exit 1

  # 5. Deploy to Production (manual approval)
  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment: production  # GitHub Environment -- manual approval gerektirir
    steps:
      - name: Deploy to production
        run: |
          echo "Deploying ${{ github.sha }} to production..."

      - name: Health check
        run: |
          sleep 30
          curl -f https://api.example.com/health || exit 1

      - name: Notify
        run: |
          echo "Deployment successful!"

12.3 Branch Stratejisi

GitFlow vs Trunk-Based Development

ÖzellikGitFlowTrunk-Based
Ana branch'lermain + developmain (tek)
Feature branchfeature/* (uzun omurlu)Kisa omurlu (1-2 gun max)
Releaserelease/* branchmain'den direkt (tag ile)
Hotfixhotfix/* branchmain'den direkt
KarmasiklikYüksek (cok branch)Düşük (tek branch)
Merge conflictSik (uzun branch'ler)Nadir (kisa branch'ler)
CI/CD uyumuOrtaYüksek
Takimda kisi5-20 (büyük takimlar)2-10 (küçük-orta takimlar)
Deploy sikligiHaftada/ayda birGunde birden fazla
Feature flagsGenellikle gereksizGerekli olabilir
GitFlow:
main ─────────────────────────────────────────────────
  │                                  ▲
  └──► develop ──────────────────────┤
          │        ▲       ▲         │
          └──► feature/x ──┘       release/1.0
          └──► feature/y ──┘

Trunk-Based:
main ─────────────────────────────────────────────────
  │   ▲   │   ▲   │   ▲
  └─► ┘   └─► ┘   └─► ┘
  short   short   short
  lived   lived   lived
  branch  branch  branch
  (1-2d)  (1-2d)  (1-2d)

12.4 Environment'lar

EnvironmentAmacVeriErisimDeploy
DevelopmentGeliştirme, testFake/seed dataGelistiricilerHer commit
StagingPre-production testProduction benzeri (anonimlestirilmis)Takim + QAdevelop/main merge
ProductionCanli kullanicilarGercek veriHerkesManuel onay
typescript
// Environment-based konfigürasyon
// config/index.ts
interface Config {
  port: number;
  database: {
    url: string;
    ssl: boolean;
    pool: { min: number; max: number };
  };
  redis: { url: string };
  logging: { level: string };
  cors: { origins: string[] };
}

const configs: Record<string, Config> = {
  development: {
    port: 3000,
    database: {
      url: 'postgresql://dev:dev@localhost:5432/myapp_dev',
      ssl: false,
      pool: { min: 2, max: 10 }
    },
    redis: { url: 'redis://localhost:6379' },
    logging: { level: 'debug' },
    cors: { origins: ['http://localhost:3000', 'http://localhost:5173'] }
  },
  staging: {
    port: 3000,
    database: {
      url: process.env.DATABASE_URL!,
      ssl: true,
      pool: { min: 5, max: 20 }
    },
    redis: { url: process.env.REDIS_URL! },
    logging: { level: 'info' },
    cors: { origins: ['https://staging.example.com'] }
  },
  production: {
    port: 3000,
    database: {
      url: process.env.DATABASE_URL!,
      ssl: true,
      pool: { min: 10, max: 50 }
    },
    redis: { url: process.env.REDIS_URL! },
    logging: { level: 'warn' },
    cors: { origins: ['https://example.com', 'https://www.example.com'] }
  }
};

export const config = configs[process.env.NODE_ENV || 'development'];

12.5 Deployment Stratejileri

StratejiAçıklamaDowntimeRollbackRiskMaliyet
RecreateEski durdur, yeni baslatVarYavasYüksekDüşük
RollingPod'lari sirayla güncelleYokOrtaOrtaDüşük
Blue-GreenIki ortam, traffic swapYokHızlı (switch)DüşükYüksek (2x kaynak)
Canary%5 trafige yeni versiyon, yavas artirYokHızlı (geri cek)DüşükOrta
A/B TestingKullanici segmentine gore farkli versiyonYokHızlıDüşükOrta
Blue-Green Deployment:

Onceki:  [Load Balancer] ──────► [Blue (v1.0) AKTIF]
                                  [Green (bos)]

Deploy:  [Load Balancer] ──────► [Blue (v1.0) AKTIF]
                                  [Green (v1.1) hazirlaniyor...]

Switch:  [Load Balancer] ──────► [Green (v1.1) AKTIF]
                                  [Blue (v1.0) yedek -- sorun olursa geri don]


Canary Deployment:

Basla:   [Load Balancer] ──100%──► [v1.0 (10 pod)]

Canary:  [Load Balancer] ───95%──► [v1.0 (10 pod)]
                          ───5%───► [v1.1 (1 pod)]   ← Metrikler izleniyor

Genisle: [Load Balancer] ───50%──► [v1.0 (5 pod)]
                          ───50%──► [v1.1 (5 pod)]   ← Hala iyi

Tamam:   [Load Balancer] ──100%──► [v1.1 (10 pod)]

13) Güvenlik Checklist (OWASP Top 10)

13.1 OWASP Top 10 (2021)

SiralamaZafiyetAçıklamaKorunma Yontemi
A01Broken Access ControlYetkisiz kaynaklara erisimRBAC, her endpoint'te yetki kontrolu, IDOR korunmasi
A02Cryptographic FailuresZayif sifreleme, hassas veri aciga cikmaHTTPS zorunlu, AES-256 sifreleme, bcrypt/argon2
A03InjectionSQL, NoSQL, OS, LDAP injectionParametreli sorgular, ORM kullan, input sanitize et
A04Insecure DesignGüvenlik mimari seviyede dusunulmemisThreat modeling, güvenli tasarim prensipleri
A05Security MisconfigurationVarsayilan ayarlar, gereksiz ozellikler acikSertlestirme checklist'i, gereksiz servisleri kapat
A06Vulnerable ComponentsBilinen zafiyetli kutuphanelernpm audit, Dependabot, Snyk
A07Auth FailuresZayif kimlik doğrulamaMFA, güvenli oturum yönetimi, brute-force korunma
A08Data Integrity FailuresGuncellemelerin butunlugu dogrulanmiyorSigned updates, CI/CD pipeline guvenceleri
A09Logging FailuresYetersiz loglama ve izlemeStructured logging, SIEM, alert sistemi
A10SSRFSunucu tarafinda sahte isteklerURL whitelist, internal network erisimini kisitla

Injection Korunma

typescript
// YANLIS: SQL Injection'a acik
const query = `SELECT * FROM users WHERE email = '${email}'`;
// Saldirgan email: ' OR '1'='1' --
// Sonuc: SELECT * FROM users WHERE email = '' OR '1'='1' --'

// DOGRU: Parametreli sorgu
const result = await db.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
);

// DOGRU: ORM kullan (Prisma)
const user = await prisma.user.findUnique({ where: { email } });

// DOGRU: ORM kullan (Knex)
const user = await knex('users').where({ email }).first();

XSS Korunma

typescript
// Input sanitization
import DOMPurify from 'isomorphic-dompurify';

// Kullanici girdisini temizle
function sanitizeInput(input: string): string {
  return DOMPurify.sanitize(input, {
    ALLOWED_TAGS: [],         // HTML tag'i kalmasin
    ALLOWED_ATTR: []
  });
}

// HTML iceren icerik (blog, yorum)
function sanitizeRichContent(html: string): string {
  return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
    ALLOWED_ATTR: ['href', 'target']
  });
}

// Security headers
import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: []
    }
  },
  crossOriginEmbedderPolicy: true,
  crossOriginOpenerPolicy: true,
  crossOriginResourcePolicy: { policy: 'same-site' },
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
  noSniff: true,
  referrerPolicy: { policy: 'strict-origin-when-cross-origin' }
}));

13.2 Production Güvenlik Checklist

Kimlik Doğrulama & Yetkilendirme:

  • [ ] Sifreleri bcrypt/argon2 ile hashle (salt ile)
  • [ ] JWT secret en az 256-bit, .env'den oku
  • [ ] Access token süresi kisa tut (15 dk)
  • [ ] Refresh token rotation uygula
  • [ ] Brute-force korunmasi (hesap kilitleme veya rate limit)
  • [ ] MFA (multi-factor authentication) destegi
  • [ ] OAuth2 PKCE kullan (SPA/mobil için)
  • [ ] Her endpoint'te yetki kontrolu yap

Veri Guvenligi:

  • [ ] HTTPS zorunlu (HTTP redirect)
  • [ ] Hassas veriler sifrelenmis saklanir (AES-256)
  • [ ] API response'larinda hassas alan yok (password_hash, secret)
  • [ ] CORS sadece izin verilen origin'ler
  • [ ] SQL injection korunmasi (parametreli sorgu / ORM)
  • [ ] XSS korunmasi (input sanitization + CSP header)
  • [ ] CSRF korunmasi (SameSite cookie veya CSRF token)

Altyapi & Konfigürasyon:

  • [ ] .env dosyasi git'e commitlenmemis (.gitignore)
  • [ ] Production'da debug modu kapali
  • [ ] Error mesajlarinda stack trace gosterilmiyor
  • [ ] Rate limiting aktif (genel + login endpoint)
  • [ ] Security headers (helmet.js) aktif
  • [ ] Dependency audit (npm audit, Snyk) duzeltilmis
  • [ ] Gereksiz portlar kapali
  • [ ] Varsayilan sifre/kullanici adi degistirilmis

Loglama & İzleme:

  • [ ] Güvenlik olaylari loglanir (login, basarisiz giris, yetki hatasi)
  • [ ] Log'lar merkezi sistemde toplanir
  • [ ] Alarm/alert sistemi kurulmus
  • [ ] Sentry veya benzeri error tracking aktif

14) Performance & Scalability

14.1 Profiling (Darboğaz Tespiti)

typescript
// Basit profiling -- islem suresi olcumu
function measurePerformance(label: string) {
  const start = process.hrtime.bigint();

  return {
    end: () => {
      const end = process.hrtime.bigint();
      const durationMs = Number(end - start) / 1_000_000;
      console.log(`[PERF] ${label}: ${durationMs.toFixed(2)}ms`);
      return durationMs;
    }
  };
}

// Kullanim
app.get('/api/products', async (req, res) => {
  const perf = measurePerformance('GET /api/products');

  const dbPerf = measurePerformance('DB Query');
  const products = await productRepo.findAll(req.query);
  dbPerf.end();

  const cachePerf = measurePerformance('Cache Write');
  await cache.set('products:list', JSON.stringify(products));
  cachePerf.end();

  perf.end();
  // Cikti:
  // [PERF] DB Query: 45.23ms
  // [PERF] Cache Write: 2.15ms
  // [PERF] GET /api/products: 48.67ms

  res.json({ success: true, data: products });
});
typescript
// Express middleware -- tum isteklerin suresini logla
app.use((req: Request, res: Response, next: NextFunction) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    const logLevel = duration > 1000 ? 'warn' : 'info'; // 1 saniyeden uzunsa warn

    logger[logLevel]({
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      duration: `${duration}ms`,
      contentLength: res.get('content-length')
    });
  });

  next();
});

14.2 Horizontal vs Vertical Scaling

ÖzellikVertical Scaling (Scale Up)Horizontal Scaling (Scale Out)
YaklasimDaha guclu makine (CPU, RAM)Daha fazla makine
LimitFiziksel donanim limitiTeorik olarak sinirsiz
MaliyetUstel artar (2x guc = 3x fiyat)Dogrusal artar
DowntimeGerekebilir (upgrade)Gerekli degil
KarmasiklikDüşükYüksek (load balancing, state yönetimi)
State yönetimiBasit (tek makine)Karmasik (paylasilmis state)
VeritabaniRead replicas, bigger instanceSharding, partitioning
Örnekt2.micro -> t2.xlarge1 instance -> 10 instance

14.3 Load Balancing Stratejileri

StratejiAçıklamaNe Zaman Kullan
Round RobinIstekleri sirayla dagitSunucular esit kapasitede
Weighted Round RobinAgirlikla dagit (guclu sunucuya fazla)Farkli kapasiteli sunucular
Least ConnectionsEn az aktif baglantisi olana gonderUzun sureli istekler
IP HashAyni IP hep ayni sunucuyaSession affinity (sticky session)
RandomRastgele dagitBasit, esit dağıtım
nginx
# Nginx load balancing ornegi
upstream api_servers {
    # Weighted Round Robin
    server api1.internal:3000 weight=3;  # 3 kat fazla trafik alir
    server api2.internal:3000 weight=2;
    server api3.internal:3000 weight=1;

    # Health check
    # Yanit vermezse 30 saniye devre disi birak
    server api4.internal:3000 backup;  # Yedek sunucu
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://api_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeout ayarlari
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
        proxy_send_timeout 30s;
    }
}

14.4 APM Araçları (Application Performance Monitoring)

AracÖzellikAvantajMaliyet
New RelicFull-stack APM, error trackingKapsamli, kolay kurulumUcretli (free tier var)
DatadogAPM, logs, metrics, tracingGuclu dashboard, entegrasyonlarUcretli
Prometheus + GrafanaMetrics toplama + gorsellestirmeAcik kaynak, ozellestirilebilirÜcretsiz (hosting maliyeti)
Elastic APMAPM, ELK stack entegrasyonuLog + APM tek yerdeAcik kaynak + bulut opsiyonu
typescript
// Prometheus metrics ornegi
import promClient from 'prom-client';

// Default metrikleri topla (CPU, memory, event loop)
promClient.collectDefaultMetrics({ prefix: 'myapp_' });

// Ozel metrikler
const httpRequestDuration = new promClient.Histogram({
  name: 'myapp_http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 5]
});

const httpRequestTotal = new promClient.Counter({
  name: 'myapp_http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code']
});

const activeConnections = new promClient.Gauge({
  name: 'myapp_active_connections',
  help: 'Number of active connections'
});

// Middleware
app.use((req, res, next) => {
  activeConnections.inc();
  const end = httpRequestDuration.startTimer();

  res.on('finish', () => {
    const route = req.route?.path || req.path;
    const labels = { method: req.method, route, status_code: res.statusCode.toString() };

    end(labels);
    httpRequestTotal.inc(labels);
    activeConnections.dec();
  });

  next();
});

// Metrics endpoint (Prometheus scrape edecek)
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', promClient.register.contentType);
  res.send(await promClient.register.metrics());
});

15) Testing Stratejisi

15.1 Test Piramidi

         /  E2E Tests  \          Yavas, pahali, az
        / (Cypress,     \         Tum sistemi test eder
       /  Playwright)    \        5-10% of tests
      /───────────────────\
     / Integration Tests   \      DB, API, servisler arasi
    / (Supertest, Prisma    \     20-30% of tests
   /  test containers)      \
  /──────────────────────────\
 /      Unit Tests            \   Hizli, ucuz, cok
/  (Jest, Vitest, Mocha)       \  60-70% of tests
/──────────────────────────────\
Test TuruNe Test EderHizMaliyetÖrnek
UnitTek fonksiyon/sınıf (izole)Cok hızlı (ms)DüşükcalculateTotal()
IntegrationBirden fazla birim birlikteOrta (saniye)OrtaAPI endpoint + DB
E2ETüm sistem (kullanici gibi)Yavas (dakika)YüksekLogin -> Siparis -> Odeme

15.2 Unit Test

AAA Pattern (Arrange, Act, Assert)

typescript
// Jest / Vitest ornegi
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { PricingService } from '../services/PricingService';

describe('PricingService', () => {
  let pricingService: PricingService;

  beforeEach(() => {
    pricingService = new PricingService();
  });

  describe('calculateTotal', () => {
    it('should calculate total for regular customer', () => {
      // Arrange (Hazirla)
      const basePrice = 100;
      const quantity = 5;
      const customerType = 'regular';

      // Act (Calistir)
      const total = pricingService.calculatePrice(basePrice, {
        quantity,
        customerType
      });

      // Assert (Dogrula)
      expect(total).toBe(500);
    });

    it('should apply 15% discount for premium customer', () => {
      // Arrange
      const basePrice = 100;
      const quantity = 5;

      // Act
      const total = pricingService.calculatePrice(basePrice, {
        quantity,
        customerType: 'premium'
      });

      // Assert
      expect(total).toBe(425); // 500 * 0.85
    });

    it('should apply volume discount for wholesale (50+ items)', () => {
      // Arrange & Act
      const total = pricingService.calculatePrice(100, {
        quantity: 50,
        customerType: 'wholesale'
      });

      // Assert
      expect(total).toBe(4000); // 5000 * 0.80 (20% indirim)
    });

    it('should throw error for negative price', () => {
      // Arrange & Act & Assert
      expect(() => {
        pricingService.calculatePrice(-100, { quantity: 1, customerType: 'regular' });
      }).toThrow('Price must be positive');
    });
  });
});

Mock, Stub, Spy Farki

TerimAçıklamaÖrnek
MockSahte nesne, davranisi programlanmisjest.fn().mockResolvedValue(user)
StubBelirli bir döngü degeri olan sahtegetUser = () => ({ id: 1, name: 'Test' })
SpyGercek fonksiyonu izler (cagirildi mi, kac kez?)jest.spyOn(emailService, 'send')
typescript
// Mock ornegi -- bagimliliklari taklit et
describe('UserService', () => {
  it('should create user and send welcome email', async () => {
    // Mock repository
    const mockUserRepo = {
      create: jest.fn().mockResolvedValue({
        id: '1',
        name: 'Test User',
        email: 'test@test.com'
      }),
      findByEmail: jest.fn().mockResolvedValue(null) // Email kullanilmamis
    };

    // Mock email service
    const mockEmailService = {
      sendWelcome: jest.fn().mockResolvedValue(undefined)
    };

    const userService = new UserService(
      mockUserRepo as any,
      mockEmailService as any
    );

    // Act
    const user = await userService.register({
      name: 'Test User',
      email: 'test@test.com',
      password: 'password123'
    });

    // Assert
    expect(user.id).toBe('1');
    expect(mockUserRepo.create).toHaveBeenCalledTimes(1);
    expect(mockEmailService.sendWelcome).toHaveBeenCalledWith('test@test.com', 'Test User');
  });

  it('should throw error if email already exists', async () => {
    const mockUserRepo = {
      findByEmail: jest.fn().mockResolvedValue({ id: '1', email: 'test@test.com' })
    };

    const userService = new UserService(mockUserRepo as any, {} as any);

    await expect(
      userService.register({
        name: 'Test',
        email: 'test@test.com',
        password: 'password123'
      })
    ).rejects.toThrow('Email already in use');
  });
});
typescript
// Spy ornegi -- gercek fonksiyonu izle
describe('OrderService', () => {
  it('should log order creation', async () => {
    const loggerSpy = jest.spyOn(logger, 'info');

    await orderService.createOrder({
      userId: '1',
      items: [{ productId: 'p1', quantity: 2 }]
    });

    expect(loggerSpy).toHaveBeenCalledWith(
      expect.stringContaining('Order created'),
      expect.objectContaining({ userId: '1' })
    );

    loggerSpy.mockRestore(); // Spy'i temizle
  });
});

15.3 Integration Test

typescript
// Supertest ile API integration test
import request from 'supertest';
import { app } from '../app';
import { db } from '../database';

describe('Product API', () => {
  // Her test oncesi veritabanini temizle
  beforeEach(async () => {
    await db.query('DELETE FROM products');
    await db.query('DELETE FROM categories');

    // Test verisi ekle
    await db.query(
      `INSERT INTO categories (id, name) VALUES ('cat-1', 'Electronics')`
    );
  });

  afterAll(async () => {
    await db.end();
  });

  describe('POST /api/products', () => {
    it('should create a new product', async () => {
      const response = await request(app)
        .post('/api/products')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({
          name: 'iPhone 15',
          description: 'Apple iPhone 15',
          price: 49999.99,
          stock: 100,
          categoryId: 'cat-1'
        })
        .expect(201);

      expect(response.body.success).toBe(true);
      expect(response.body.data.name).toBe('iPhone 15');
      expect(response.body.data.price).toBe(49999.99);
      expect(response.body.data.slug).toBe('iphone-15');

      // Veritabaninda kontrol
      const dbProduct = await db.query(
        'SELECT * FROM products WHERE name = $1',
        ['iPhone 15']
      );
      expect(dbProduct.rows).toHaveLength(1);
    });

    it('should return 422 for invalid price', async () => {
      const response = await request(app)
        .post('/api/products')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({
          name: 'Test Product',
          price: -10,
          stock: 5,
          categoryId: 'cat-1'
        })
        .expect(422);

      expect(response.body.success).toBe(false);
      expect(response.body.message).toContain('Price');
    });

    it('should return 401 without auth token', async () => {
      await request(app)
        .post('/api/products')
        .send({ name: 'Test' })
        .expect(401);
    });

    it('should return 403 for non-admin user', async () => {
      await request(app)
        .post('/api/products')
        .set('Authorization', `Bearer ${userToken}`)
        .send({ name: 'Test' })
        .expect(403);
    });
  });

  describe('GET /api/products', () => {
    beforeEach(async () => {
      // Test urunleri ekle
      await db.query(
        `INSERT INTO products (id, name, slug, price, stock, category_id, is_active, created_at)
         VALUES
         ('p1', 'iPhone 15', 'iphone-15', 49999.99, 100, 'cat-1', true, NOW()),
         ('p2', 'MacBook Pro', 'macbook-pro', 89999.99, 50, 'cat-1', true, NOW()),
         ('p3', 'AirPods', 'airpods', 9999.99, 200, 'cat-1', true, NOW())`
      );
    });

    it('should list products with pagination', async () => {
      const response = await request(app)
        .get('/api/products?page=1&limit=2')
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.items).toHaveLength(2);
      expect(response.body.meta.total).toBe(3);
      expect(response.body.meta.totalPages).toBe(2);
    });

    it('should filter products by search', async () => {
      const response = await request(app)
        .get('/api/products?search=macbook')
        .expect(200);

      expect(response.body.items).toHaveLength(1);
      expect(response.body.items[0].name).toBe('MacBook Pro');
    });

    it('should sort products by price ascending', async () => {
      const response = await request(app)
        .get('/api/products?sortBy=price&sortOrder=asc')
        .expect(200);

      const prices = response.body.items.map((p: any) => p.price);
      expect(prices).toEqual([...prices].sort((a, b) => a - b));
    });
  });
});

15.4 E2E Test (End-to-End)

typescript
// Playwright E2E test ornegi
import { test, expect } from '@playwright/test';

test.describe('Siparis Akisi', () => {
  test.beforeEach(async ({ page }) => {
    // Giris yap
    await page.goto('/login');
    await page.fill('[name="email"]', 'test@example.com');
    await page.fill('[name="password"]', 'password123');
    await page.click('button[type="submit"]');
    await page.waitForURL('/dashboard');
  });

  test('kullanici urun satin alabilmeli', async ({ page }) => {
    // 1. Urunu bul
    await page.goto('/products');
    await page.fill('[placeholder="Urun ara..."]', 'iPhone');
    await page.click('text=iPhone 15');

    // 2. Sepete ekle
    await page.click('button:has-text("Sepete Ekle")');
    await expect(page.locator('.cart-badge')).toHaveText('1');

    // 3. Sepete git
    await page.click('.cart-icon');
    await expect(page.locator('.cart-item')).toHaveCount(1);

    // 4. Odeme yap
    await page.click('button:has-text("Odemeye Gec")');

    // 5. Adres bilgisi
    await page.fill('[name="address"]', 'Test Mah. Test Sk. No:1');
    await page.fill('[name="city"]', 'Istanbul');
    await page.click('button:has-text("Devam")');

    // 6. Kart bilgisi
    await page.fill('[name="cardNumber"]', '4111111111111111');
    await page.fill('[name="expiry"]', '12/26');
    await page.fill('[name="cvv"]', '123');
    await page.click('button:has-text("Odemeyi Tamamla")');

    // 7. Basari sayfasi
    await expect(page.locator('h1')).toHaveText('Siparisiz Alindi!');
    await expect(page.locator('.order-id')).toBeVisible();
  });
});

15.5 Test Coverage Hedefi

MetrikMinimumIdealAçıklama
Line Coverage%70%85+Satirlarin kaci calistirildi
Branch Coverage%60%80+if/else dallarinin kaci test edildi
Function Coverage%70%90+Fonksiyonlarin kaci cagrildi
Statement Coverage%70%85+Ifadelerin kaci calistirildi
json
// jest.config.js veya package.json
{
  "jest": {
    "coverageThreshold": {
      "global": {
        "branches": 70,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    },
    "collectCoverageFrom": [
      "src/**/*.{ts,js}",
      "!src/**/*.d.ts",
      "!src/**/*.test.{ts,js}",
      "!src/**/index.{ts,js}",
      "!src/migrations/**"
    ]
  }
}

Önemli Not

%100 coverage hedefleme. %100 coverage her seyin doğru test edildigini garanti etmez. Anlamli testler yazmak, coverage rakami yukseltmekten daha onemlidir. "Getters/setters test etme" gibi anlamsiz testler coverage'i siser ama deger katmaz.

15.6 CI'da Test

yaml
# GitHub Actions'da test
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run test -- --coverage --ci
      - run: npm run test:e2e

      # Coverage raporunu comment olarak PR'a ekle
      - name: Coverage Report
        uses: davelosert/vitest-coverage-report-action@v2
        if: always()

16) Hızlı Referans

16.1 Pattern Secim Tablosu

SorunÖnerilen Pattern
Tek instance gerekli (DB, logger)Singleton veya Module pattern
Nesne oluşturma karmasikFactory
Cok parametreli nesneBuilder
3. parti kutuphaney sarmakAdapter
Mevcut nesneye özellik eklemekDecorator
Karmasik alt sistemi basitlestirmekFacade
Erisim kontrolu, lazy loadingProxy
Olay bildirimi, event sistemiObserver
Degistirilebilir algoritmaStrategy
Undo/redo, kuyruge almaCommand
Durum bazli davranisState
Is/veri boru hattiPipeline / Chain of Responsibility

16.2 HTTP Status Code Referans

2xx Basari:
  200 OK                 -- Basarili istek
  201 Created            -- Kaynak olusturuldu
  204 No Content         -- Basarili ama icerik yok

3xx Yonlendirme:
  301 Moved Permanently  -- Kalici tasinma
  304 Not Modified       -- Cache gecerli

4xx Client Hatasi:
  400 Bad Request        -- Hatali istek
  401 Unauthorized       -- Kimlik dogrulanamadi
  403 Forbidden          -- Yetki yok
  404 Not Found          -- Bulunamadi
  405 Method Not Allowed -- Metot desteklenmiyor
  409 Conflict           -- Cakisma
  422 Unprocessable      -- Anlam olarak gecersiz
  429 Too Many Requests  -- Rate limit

5xx Server Hatasi:
  500 Internal Error     -- Sunucu hatasi
  502 Bad Gateway        -- Upstream hata
  503 Service Unavailable-- Gecici kullanilamaz
  504 Gateway Timeout    -- Upstream zaman asimi

16.3 API Tasarim Checklist

  • [ ] URL'ler cogul, küçük harf, kebab-case (/api/v1/order-items)
  • [ ] HTTP metotlari doğru kullaniliyor (GET okuma, POST oluşturma...)
  • [ ] Tutarli response envelope ({ success, data, message, errors, meta })
  • [ ] Pagination implement edilmis (offset veya cursor)
  • [ ] Filtering, sorting, searching destegi
  • [ ] Input validation (request body, query params)
  • [ ] Anlamli HTTP status code'lari (201 Created, 404 Not Found...)
  • [ ] Rate limiting aktif
  • [ ] CORS doğru ayarlanmis
  • [ ] API versiyonlama (/api/v1/...)
  • [ ] Authentication gerekli endpoint'ler korunmus
  • [ ] Error response'lar tutarli format
  • [ ] Swagger/OpenAPI dokumantasyonu
  • [ ] Request/response loglama

16.4 Production Deployment Checklist

Deploy Oncesi:

  • [ ] Tüm testler geciyor (unit, integration, E2E)
  • [ ] Lint hataları yok
  • [ ] Type check basarili
  • [ ] Migration'lar hazir
  • [ ] Environment degiskenleri ayarlandi
  • [ ] Rollback plani var
  • [ ] Takim bilgilendirildi

Deploy Sirasinda:

  • [ ] Staging'de test edildi
  • [ ] Blue-green veya canary deployment
  • [ ] Migration'lar uygulandi
  • [ ] Health check endpoint calisiyor

Deploy Sonrasi:

  • [ ] Health check basarili
  • [ ] Smoke test (kritik akislar) basarili
  • [ ] Error rate normal
  • [ ] Response time normal
  • [ ] Log'larda beklenmedik hata yok
  • [ ] Monitoring dashboard kontrol edildi
  • [ ] Kullanicilara duyuru (gerekiyorsa)

Ilgili Rehberler

Backend

Diger Kategoriler

Developer Guides & Technical References