İçeriğe atla

Effect Öğrenmek: TypeScript Geliştiricileri için Pratik Bir Rehber

Effect'i adım adım öğrenmek ve AWS Lambda ile entegre etmek için pratik bir rehber: gerçek kod örnekleri, yaygın hatalar ve üretim desenleri.

Ayhan Sipahi Ayhan Sipahi

Effect, TypeScript’e fonksiyonel bir effect sistemi getiriyor: typed error’lar, dependency injection ve structured concurrency, hepsini compiler zorunlu tutuyor. Ekosistem zengin ama öğrenme eğrisi dik. Dokümantasyon Discord tartışmalarına, GitHub issue’larına ve farklı tarihlerden blog yazılarına dağılmış durumda; “Hello World”den production’daki bir Lambda’nın arkasına koyacağın koda giden yol bu yüzden belirgin değil.

Başlangıç için makul varsayılan kademeli benimseme: yeni özellikleri ve karmaşıklığı yüksek modülleri Effect ile yaz, mevcut Promise tabanlı stabil kodu dokunmak için bir sebep çıkana kadar olduğu gibi bırak. Yolun tamamı için gerçekçi bütçe on iki hafta; bunun iki ila dört haftası temellere gidiyor, servis layer’ları, concurrency ve Lambda desenleri ondan sonra anlam kazanıyor.

Örtük Hataların Maliyeti#

TypeScript’in tip güvenliği compile time’da bitiyor. Şu yaygın fonksiyon imzasını düşünelim:

async function getUserById(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`)
  return await response.json()
}

Bu imza kritik bilgileri gizliyor:

  • Kullanıcı bulunamazsa ne olur?
  • Network başarısız olursa ne olur?
  • Response geçerli JSON değilse ne olur?
  • Hangi database bağlantısı gerekli?

Effect tüm bunları açık hale getiriyor:

function getUserById(id: string): Effect<User, MissingUser | NetworkError, DatabaseService> {
  // Return type: User
  // Hatalar: MissingUser VEYA NetworkError (ikisi de typed)
  // Gereksinimler: DatabaseService (sağlanması zorunlu)
}

Tip imzası artık üç kritik boyutu belgeliyor: başarı tipi (User), hata tipleri (MissingUser | NetworkError) ve bağımlılıklar (DatabaseService). Bu sadece dokümantasyon değil; compiler bunu zorunlu tutuyor.

Effect’in Vaat Ettikleri#

Effect, fp-ts’in halefi, yani fp-ts v3. Giulio Canti (fp-ts yazarı) Effect organizasyonuna katıldığında, net bir evrim yoluna işaret etti. Effect, fp-ts’in birkaç sınırlamasını ele alıyor:

Core Type: Effect<A, E, R>

  • A: Success type (effect’in ürettiği)
  • E: Error type (neyin yanlış gidebileceği; açıkça typed)
  • R: Requirements (hangi bağımlılıkların gerektiği)

fp-ts’in Ötesinde Özellikler:

  1. Structured Concurrency: Otomatik cancellation ve resource cleanup ile built-in fiber runtime
  2. Service Management: Dependency injection için Context, Tag ve Layer sistemi
  3. Built-in Utilities: Clock, Random, Console, Logger servisleri dahil
  4. Error Merging: Farklı hatalarla effect’leri birleştirirken otomatik union types
  5. Testing Infrastructure: Deterministik testler için TestClock, TestRandom, TestContext
  6. Observability: OpenTelemetry desteğiyle native metrics, tracing, logging
  7. Streams: RxJS’e benzer ama doğru resource management ile
  8. Schema: Zod’un yerini alabilen runtime validation (TypeScript 5.0+ gerektirir)

Bundle Size Gerçeği

Effect’in core’u tree-shakeable, ancak fiber runtime da paketle geldiği için initial bundle fp-ts’ten daha büyük. Onu, yerini aldığı kütüphane yığınıyla birlikte tart:

  • Zod → Effect’in Schema modülü
  • RxJS → Effect streams
  • Lodash utilities → Effect standard library
  • Custom DI framework → Layer system
  • Retry kütüphaneleri → Effect.retry
  • Promise utilities → Effect.promise, Effect.all

Net etki: kabaca nötr veya biraz daha büyük, ama dependency’leri birleştiriyorsun ve compile-time garantiler kazanıyorsun. Kendi import grafiğin üzerinde bir bundle analiz aracıyla ölç; rakam tamamen Effect’in ne kadarını çektiğine bağlı.

12 Haftalık Öğrenme Planı#

Her aşama tek başına kullanılabilir. Aşama 1’den sonra kod göndermeye başlayıp servis layer’larını sonra ekleyebilirsin; takvimin teslim baskısına dayanmasını sağlayan da bu.

Hafta 1-2: Temel

Hafta 3-4: Servisler

Hafta 5-8: İleri Desenler

Hafta 9-12: Production

Effect temelleri

Hata yönetimi

Effect.gen

Context & Tag

Layer sistemi

Config modülü

Concurrency

Streams

Resource management

AWS Lambda

Test desenleri

Observability

Aşama 1: Temel (Hafta 1-2)#

Öğrenme Hedefleri:

  • Effect<A, E, R> tip imzasını anlamak
  • Temel effect’ler oluşturmak
  • Pattern matching ile hataları yönetmek
  • Effect’leri güvenli şekilde çalıştırmak

Basit Dönüşümlerle Başla

import { Effect } from "effect"

// Geleneksel Promise tabanlı kod
async function validateEmail(email: string): Promise<string> {
  if (!email.includes("@")) {
    throw new Error("Invalid email")
  }
  return email.toLowerCase()
}

// Typed error'larla Effect tabanlı kod
import { Data } from "effect"

class InvalidEmail extends Data.TaggedError("InvalidEmail")<{
  email: string
  reason: string
}> {}

function validateEmail(email: string): Effect.Effect<string, InvalidEmail> {
  if (!email.includes("@")) {
    return Effect.fail(new InvalidEmail({
      email,
      reason: "Missing @ symbol"
    }))
  }
  return Effect.succeed(email.toLowerCase())
}

Composition için Effect.gen Kullanımı

Effect.gen, generator-style composition sağlıyor (async/await’e benzer):

const getUserProfile = (userId: string) =>
  Effect.gen(function* () {
    // yield* Effect'i unwrap ediyor
    const user = yield* getUserById(userId)
    const posts = yield* getPostsByUser(user.id)
    const analytics = yield* getAnalytics(user.id)

    return { user, posts, analytics }
  })

Pattern Matching ile Hata Yönetimi

import { Effect } from "effect"

const result = await getUserById("123").pipe(
  Effect.catchTags({
    MissingUser: (error) => Effect.succeed(defaultUser),
    DatabaseError: (error) => {
      // Log error, fallback dön
      console.error("Database failed:", error)
      return Effect.fail(new ServiceUnavailable())
    }
  }),
  Effect.runPromise
)

Aşama 2: Servis Mimarisi (Hafta 3-4)#

Öğrenme Hedefleri:

  • Context.GenericTag ile servisleri tanımlamak
  • Layer ile servis implementasyonları oluşturmak
  • Layer’ları etkili şekilde compose etmek
  • Konfigürasyonları yönetmek

Servis Interface’i Tanımla

import { Context, Effect, Layer } from "effect"

// 1. Servis interface'ini tanımla
interface DatabaseService {
  query: (sql: string, params?: unknown[]) => Effect.Effect<unknown[], QueryError>
  transaction: <A, E>(
    operation: Effect.Effect<A, E, DatabaseService>
  ) => Effect.Effect<A, E | TransactionError, DatabaseService>
}

// 2. Servis tag'i oluştur (Context.GenericTag daha yeni pattern; Context.Tag da çalışıyor)
const DatabaseService = Context.GenericTag<DatabaseService>("DatabaseService")

// 3. Effect'lerde servisi kullan
const getUser = (id: string) =>
  Effect.gen(function* () {
    const db = yield* DatabaseService
    const rows = yield* db.query(`SELECT * FROM users WHERE id = $1`, [id])

    if (rows.length === 0) {
      return yield* Effect.fail(new MissingUser({ userId: id }))
    }

    return rows[0] as User
  })

Servis Layer’ı Implement Et

import { Layer } from "effect"
import { Pool } from "pg"

const DatabaseServiceLive = Layer.scoped(
  DatabaseService,
  Effect.gen(function* () {
    // Konfigürasyonu al
    const config = yield* Config.all({
      host: Config.string("DB_HOST"),
      port: Config.number("DB_PORT"),
      database: Config.string("DB_NAME")
    })

    // Cleanup ile connection oluştur
    const pool = yield* Effect.acquireRelease(
      Effect.sync(() => new Pool(config)),
      (pool) => Effect.promise(() => pool.end())
    )

    return DatabaseService.of({
      query: (sql, params) => Effect.tryPromise({
        try: () => pool.query(sql, params).then(r => r.rows),
        catch: (error) => new QueryError({ cause: error })
      }),
      transaction: (operation) => {
        // Implementation detayları...
        return operation
      }
    })
  })
)

Birden Fazla Servisi Compose Et

// Servis tanımlamaları
const UserService = Context.GenericTag<UserService>("UserService")
const EmailService = Context.GenericTag<EmailService>("EmailService")

// Layer implementasyonları
const UserServiceLive = Layer.effect(
  UserService,
  Effect.gen(function* () {
    const db = yield* DatabaseService
    return UserService.of({
      getById: (id) => {/* implementation */},
      create: (data) => {/* implementation */}
    })
  })
)

const EmailServiceLive = Layer.succeed(
  EmailService,
  EmailService.of({
    send: (to, subject, body) => {/* implementation */}
  })
)

// Layer'ları compose et
const AppLayer = Layer.mergeAll(
  DatabaseServiceLive,
  UserServiceLive,
  EmailServiceLive
)

// Programı tüm dependency'lerle çalıştır
const program = Effect.gen(function* () {
  const userService = yield* UserService
  const emailService = yield* EmailService

  const user = yield* userService.create({ email: "user@example.com" })
  yield* emailService.send(user.email, "Hoş geldin!", "Kaydolduğun için teşekkürler")

  return user
})

await program.pipe(Effect.provide(AppLayer), Effect.runPromise)

Aşama 3: İleri Desenler (Hafta 5-8)#

Concurrent Operations

// Sequential (yavaş)
const getDashboardSequential = (userId: string) =>
  Effect.gen(function* () {
    const user = yield* userService.getById(userId)
    const posts = yield* postService.getByUserId(userId)
    const analytics = yield* analyticsService.getMetrics(userId)
    return { user, posts, analytics }
  })

// Concurrent (hızlı)
const getDashboardConcurrent = (userId: string) =>
  Effect.gen(function* () {
    // Tüm operasyonlar concurrent çalışıyor
    // Biri fail olursa, hepsi otomatik cancel ediliyor
    const [user, posts, analytics] = yield* Effect.all(
      [
        userService.getById(userId),
        postService.getByUserId(userId),
        analyticsService.getMetrics(userId)
      ],
      { concurrency: "unbounded" }
    )

    return { user, posts, analytics }
  })

// Bounded concurrency
const processItems = (items: Item[]) =>
  Effect.all(
    items.map(processItem),
    { concurrency: 10 } // Aynı anda 10 tane işle
  )

Retry ve Timeout Stratejileri

import { Schedule } from "effect"

const robustApiCall = (url: string) =>
  Effect.gen(function* () {
    const response = yield* httpClient.get(url)
    return response
  }).pipe(
    // Exponential backoff ile retry
    Effect.retry({
      times: 3,
      schedule: Schedule.exponential("100 millis"),
      while: (error) => error._tag === "NetworkError" // Sadece network error'larda retry
    }),
    // 5 saniye sonra timeout
    Effect.timeout("5 seconds"),
    // Timeout'u handle et
    Effect.catchTag("TimeoutException", () =>
      Effect.fail(new ServiceTimeout({ url }))
    )
  )

Scope ile Resource Management

const withDatabaseConnection = <A, E>(
  operation: (conn: Connection) => Effect.Effect<A, E>
): Effect.Effect<A, E | ConnectionError> =>
  Effect.gen(function* () {
    // acquireRelease cleanup'ı garantiliyor
    const conn = yield* Effect.acquireRelease(
      connectToDatabase(),
      (conn) => Effect.sync(() => conn.close())
    )

    return yield* operation(conn)
  })

// Kullanım
const result = yield* withDatabaseConnection((conn) =>
  Effect.gen(function* () {
    const user = yield* queryUser(conn, userId)
    const posts = yield* queryPosts(conn, userId)
    return { user, posts }
  })
)

Aşama 4: AWS Lambda ile Production (Hafta 9-12)#

Effect Neden Lambda ile İyi Çalışıyor:

  1. Layer sistemi runtime overhead olmadan temiz DI sağlıyor
  2. acquireRelease, Lambda shutdown’da finalizer’ların çalışmasını garantiliyor
  3. Structured logging için AWS Powertools entegrasyonu
  4. Tip güvenliği configuration error’ları compile time’da yakalıyor
  5. Mock servis layer’larıyla kolay test

Temel Lambda Handler

import { EffectHandler, makeLambda } from "@effect-aws/lambda"
import { Effect } from "effect"
import type { APIGatewayProxyEvent } from "aws-lambda"

const handler: EffectHandler<APIGatewayProxyEvent, never> = (event, context) =>
  Effect.succeed({
    statusCode: 200,
    body: JSON.stringify({
      message: "Effect'ten merhaba!",
      requestId: context.requestId
    })
  })

export const main = makeLambda(handler)

Servisler ve Layer’larla

import { EffectHandler, makeLambda } from "@effect-aws/lambda"
import * as Logger from "@effect-aws/powertools-logger"
import { Context, Effect, Layer, Data } from "effect"

// Servisi tanımla
interface OrderService {
  process: (orderId: string) => Effect.Effect<Order, OrderError>
}

const OrderService = Context.GenericTag<OrderService>("OrderService")

// Error tipleri
class OrderNotFound extends Data.TaggedError("OrderNotFound")<{
  orderId: string
}> {}

class ProcessingFailed extends Data.TaggedError("ProcessingFailed")<{
  orderId: string
  reason: string
}> {}

type OrderError = OrderNotFound | ProcessingFailed

// Servis implementasyonu
const OrderServiceLive = Layer.effect(
  OrderService,
  Effect.gen(function* () {
    const dynamodb = yield* DynamoDBService
    const sns = yield* SNSService

    return OrderService.of({
      process: (orderId) =>
        Effect.gen(function* () {
          yield* Logger.logInfo("Sipariş işleniyor", { orderId })

          const order = yield* dynamodb.getItem("orders", orderId).pipe(
            Effect.catchTag("ItemNotFound", () =>
              Effect.fail(new OrderNotFound({ orderId }))
            )
          )

          // Business logic
          const processedOrder = { ...order, status: "processed" }

          yield* dynamodb.putItem("orders", processedOrder)
          yield* sns.publish("order-processed", processedOrder)

          yield* Logger.logInfo("Sipariş başarıyla işlendi", { orderId })

          return processedOrder
        })
    })
  })
)

// Lambda handler
const processOrderHandler: EffectHandler<SQSEvent, OrderService> = (event, context) =>
  Effect.gen(function* () {
    const orderService = yield* OrderService

    for (const record of event.Records) {
      const { orderId } = JSON.parse(record.body)

      yield* orderService.process(orderId).pipe(
        Effect.catchTags({
          OrderNotFound: (error) =>
            Logger.logWarn("Sipariş bulunamadı, atlanıyor", error),
          ProcessingFailed: (error) =>
            Effect.gen(function* () {
              yield* Logger.logError("İşleme başarısız, DLQ'ya gönderiliyor", error)
              // Dead letter queue'ya gönder
            })
        })
      )
    }

    return { statusCode: 200 }
  })

// Layer'ları compose et
const LambdaLayer = Layer.mergeAll(
  OrderServiceLive,
  DynamoDBServiceLive,
  SNSServiceLive,
  Logger.DefaultPowerToolsLoggerLayer
)

export const handler = makeLambda(processOrderHandler, LambdaLayer)

Schema Validation (Zod’un Yerini Alıyor)

// Schema, core effect paketinin içinde geliyor (3.10+)
import { Schema } from "effect"

class CreateOrderRequest extends Schema.Class<CreateOrderRequest>("CreateOrderRequest")({
  userId: Schema.String,
  items: Schema.Array(Schema.Struct({
    productId: Schema.String,
    quantity: Schema.Number.pipe(Schema.positive(), Schema.int())
  })),
  shippingAddress: Schema.Struct({
    street: Schema.String,
    city: Schema.String,
    zipCode: Schema.String.pipe(Schema.pattern(/^\d{5}$/))
  })
}) {}

const createOrderHandler: EffectHandler<APIGatewayProxyEvent, OrderService> = (event, context) =>
  Effect.gen(function* () {
    // Request body'yi parse ve validate et
    const request = yield* Schema.decodeUnknown(CreateOrderRequest)(
      JSON.parse(event.body || "{}")
    ).pipe(
      Effect.catchAll((error) =>
        Effect.succeed({
          statusCode: 400,
          body: JSON.stringify({ error: "Geçersiz request", details: error })
        })
      )
    )

    const orderService = yield* OrderService
    const order = yield* orderService.create(request)

    return {
      statusCode: 201,
      body: JSON.stringify(order)
    }
  })

Yaygın Hatalar ve Çözümler#

Hata 1: yield* Unutmak#

En can sıkıcı başlangıç hatası. TypeScript bunu her zaman yakalayamıyor:

// BAD: Yanlış - yield* eksik
const program = Effect.gen(function* () {
  const user = getUserById("123")  // Effect<User> dönüyor, User değil!
  console.log(user.name)  // Runtime error veya undefined
})

// Doğru
const program = Effect.gen(function* () {
  const user = yield* getUserById("123")  // User'a unwrap ediyor
  console.log(user.name)  // Beklendiği gibi çalışıyor
})

Çözüm: Bu pattern’i yakalamak için ESLint kuralları kur. Generator’lar içinde Effect’lerle her zaman yield* kullan.

Hata 2: Basit Kodu Over-Engineering Yapmak#

Her şeyin Effect’e ihtiyacı yok:

// BAD: Basit synchronous fonksiyon için gereksiz
const addNumbers = (a: number, b: number): Effect.Effect<number> =>
  Effect.succeed(a + b)

// Daha iyi - basit tut
const addNumbers = (a: number, b: number): number => a + b

// Hata durumları varsa Effect kullan
const divide = (a: number, b: number): Effect.Effect<number, DivisionByZero> =>
  b === 0
    ? Effect.fail(new DivisionByZero())
    : Effect.succeed(a / b)

Ders: Hata durumları, dependency’ler veya async operasyonlar olan işlemler için Effect kullan. Her yere zorlama.

Hata 3: Effect ve Promise Pattern’lerini Karıştırmak#

Tutarlılığı koru:

// BAD: Karışık kullanım
const fetchData = () =>
  Effect.gen(function* () {
    const response = yield* httpClient.get("/api/data")
    const processed = await processAsync(response)  // Promise sızıyor
    return processed
  })

// Tutarlı Effect kullanımı
const fetchData = () =>
  Effect.gen(function* () {
    const response = yield* httpClient.get("/api/data")
    const processed = yield* Effect.promise(() => processAsync(response))
    return processed
  })

Hata 4: Defect’leri Expected Error’lardan Ayırmamak#

Effect, expected error’lar (E tipi) ile defect’leri (beklenmeyen hatalar) ayırıyor:

// E tipinde sadece expected error'lar
const parseJSON = (input: string): Effect.Effect<unknown, ParseError> =>
  Effect.try({
    try: () => JSON.parse(input),
    catch: (e) => {
      // Sadece expected error'ları yakala
      if (e instanceof SyntaxError) {
        return new ParseError({ input, cause: e })
      }
      // Defect'lerin (OOM, stack overflow) crash etmesine izin ver
      throw e
    }
  })

Ders: Business logic error’ları için E tipini kullan. Defect’lerin crash edip alert vermesine izin ver.

Hata 5: Verimsiz Concurrent Operations#

Effect’in concurrency özelliklerinden faydalan:

// BAD: Sequential işleme (yavaş)
const processItems = (items: Item[]) =>
  Effect.gen(function* () {
    const results = []
    for (const item of items) {
      const result = yield* processItem(item)
      results.push(result)
    }
    return results
  })

// Bounded parallelism ile concurrent
const processItems = (items: Item[]) =>
  Effect.all(
    items.map(processItem),
    { concurrency: 10 }  // Aynı anda 10 tane işle
  )

Effect ile Test#

Effect kendi test primitive’leriyle geliyor; retry ve timeout’ları gerçekten beklemeden çalıştırabiliyorsun:

import { Effect, Layer, Schedule, TestContext } from "effect"
import { describe, it, expect } from "vitest"

describe("Retry mekanizması", () => {
  it("exponential backoff ile retry yapmalı", async () => {
    let attempts = 0

    const operation = Effect.gen(function* () {
      attempts++
      if (attempts < 3) {
        return yield* Effect.fail(new Error("Geçici hata"))
      }
      return 42
    }).pipe(
      Effect.retry({
        times: 3,
        schedule: Schedule.exponential("100 millis")
      })
    )

    const result = await operation.pipe(
      Effect.provide(TestContext.TestContext),
      Effect.runPromise
    )

    expect(result).toBe(42)
    expect(attempts).toBe(3)
  })
})

// Mock servislerle test
const TestDatabaseLayer = Layer.succeed(
  DatabaseService,
  DatabaseService.of({
    query: (sql) => Effect.succeed([{ id: "123", name: "Test Kullanıcı" }])
  })
)

it("database'den kullanıcı almalı", async () => {
  const user = await getUserById("123").pipe(
    Effect.provide(TestDatabaseLayer),
    Effect.runPromise
  )

  expect(user.name).toBe("Test Kullanıcı")
})

Lambda için Performans Optimizasyonu#

Cold Start Optimizasyonu:

// Büyük dependency'ler için dynamic import kullan
const heavyOperation = Effect.gen(function* () {
  const lib = yield* Effect.promise(() => import("heavy-lib"))
  return lib.process()
})

// Lazy servis initialization
const CacheServiceLive = Layer.scoped(
  CacheService,
  Effect.gen(function* () {
    // Sadece gerçekten kullanıldığında initialize et
    const connection = yield* Effect.acquireRelease(
      connectToRedis(),
      (conn) => Effect.promise(() => conn.disconnect())
    )
    return CacheService.of({ connection })
  })
)

Bundle Size İpuçları:

  • Tree-shaking etkin esbuild kullan
  • TypeScript’in importHelpers’ını aktif et ve tslib kur
  • Spesifik modülleri import et: import { Effect } from "effect/Effect"
  • Bundle’ı analiz araçlarıyla takip et
// tsconfig.json
{
  "compilerOptions": {
    "importHelpers": true,  // Helper'lar için tslib kullan
    "module": "ESNext",  // Tree-shaking'i aktif et
    "target": "ES2022"
  }
}

Benimseme Stratejileri#

Strateji 1: Önce Yeni Özellikler#

Yeni özellik geliştirmede Effect kullanmaya başla. Bu, mevcut stable kodu riske atmadan ekip uzmanlığı oluşturur.

// Yeni özellik: Effect ile ödeme işleme
const processPayment = (orderId: string) =>
  Effect.gen(function* () {
    const orderService = yield* OrderService
    const paymentService = yield* PaymentService

    const order = yield* orderService.getById(orderId)
    const receipt = yield* paymentService.charge(order.total)

    return receipt
  }).pipe(Effect.provide(AppLayer))

Strateji 2: Mevcut Servisleri Wrap Et#

Mevcut Promise tabanlı servisleri Effect interface’leriyle wrap et:

// Mevcut servis (olduğu gibi kal)
interface LegacyUserService {
  getUser(id: string): Promise<User>
}

// Effect wrapper
const UserServiceLive = Layer.succeed(
  UserService,
  UserService.of({
    getById: (id) => Effect.tryPromise({
      try: () => legacyUserService.getUser(id),
      catch: (e) => new UserError({ cause: e })
    })
  })
)

Strateji 3: Service-First Architecture#

Implementasyonlardan önce servis interface’lerini tanımla:

// 1. Interface'i tanımla
interface PaymentService {
  processPayment: (amount: number) => Effect.Effect<Receipt, PaymentError>
}

// 2. Tag oluştur
const PaymentService = Context.GenericTag<PaymentService>("PaymentService")

// 3. Birden fazla implementasyon
const StripePaymentServiceLive = Layer.effect(/* ... */)
const MockPaymentServiceLive = Layer.succeed(/* ... */)

// 4. Business logic implementasyondan bağımsız
const checkout = (cartId: string) =>
  Effect.gen(function* () {
    const payment = yield* PaymentService
    // Layer'lar üzerinden implementasyon değiştirilebilir
  })

Effect Ne Zaman, Alternatifler Ne Zaman#

Effect kullan:

  • Birden fazla hata senaryosu olan karmaşık business logic
  • Güçlü hata yönetimi ve observability gerekli
  • Birden fazla async operasyon ve concurrency gereksinimleri
  • Fonksiyonel programlama öğrenmek isteyen veya rahat olan ekip
  • Bakım maliyetinin önemli olduğu uzun ömürlü projeler

Plain TypeScript kullan:

  • Basit mantıkla CRUD API’ler
  • Ölçüp doğruladığın sıkı bundle size kısıtlamaları
  • Fonksiyonel programlamaya şiddetle karşı ekip
  • Kısa vadeli prototip veya throwaway script’ler
  • Basit Lambda fonksiyonları (örn. S3 → CloudWatch trigger)

Middy + Zod kullan:

  • Middleware pattern gerekli (CORS, validation, error handling)
  • Effect’ten daha basit öğrenme eğrisi istiyorsun
  • İleri seviye concurrency özelliklerine ihtiyacın yok
  • Bundle size birincil endişen

Nereden Başlamalı#

Kademeli benimseme varsayılan, çünkü ucuza başarısız oluyor. Typed error kanalı ve tek bir servis layer’ı ile yazılmış tek bir yeni modül, ekibin generator sözdizimini netlik mi yoksa gürültü mü olarak okuduğunu bir sürüm döngüsü içinde gösteriyor; üstelik bunu öğrenmek için stabil hiçbir kodu yeniden yazman gerekmiyor.

Bu varsayılanı iki yönde kır. Kod tabanı küçükse ve ekip zaten fonksiyonel desenlere istekliyse, tümden geçiş iki ayrı deyimi bir yıl boyunca yan yana taşımaktan iyi. Servis tek hata durumu olan ince bir CRUD katmanıysa Effect’i tamamen atla: Middy ve Zod, validation ile middleware’i çok daha düşük öğrenme maliyetiyle karşılıyor, gerisini de düz bir try/catch görüyor.

Kaynaklar#

İlgili yazılar