İçeriğe atla

2025-12-23

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

Effect'i anlamak, adım adım öğrenmek ve AWS Lambda ile entegre etmek için kapsamlı bir rehber. Gerçek kod örnekleri, yaygın hatalar ve üretim kullanımından pratik desenler içerir.

Özet

Effect, fonksiyonel effect sistemlerini production uygulamalarına getiren kapsamlı bir TypeScript kütüphanesi. Typed error’lar, dependency injection ve structured concurrency sağlıyor; hepsi compile time’da zorunlu tutuluyor. Bu rehber Effect’in ne olduğunu, 12 haftalık bir süreçte nasıl öğrenileceğini ve AWS Lambda ile nasıl entegre edileceğini anlatıyor. Effect ile çalışmak bana açık hata yönetiminin sadece güvenlikle ilgili olmadığını öğretti; API’leri nasıl tasarladığınızı ve hata durumları hakkında nasıl düşündüğünüzü temelden değiştiriyor. Bu yazı pratik kod örnekleri, gerçek kullanımdan yaygın hatalar ve Effect düşünen ekipler için benimseme stratejileri içeriyor.

Bu Rehber Hakkında Bir Not

Ben de Effect öğreniyorum. Ekosistem zengin ama öğrenme eğrisi dik; dokümantasyon Discord tartışmalarına, GitHub issue’larına ve sürekli güncellenen blog yazılarına dağılmış durumda. “Hello World”den production-ready koda giden net bir yol bulmak epey çaba gerektirdi. Bu rehber, başladığımda keşke elimde olsaydı dediğim yol haritası. Öğrendiklerimi pratik bir ilerleme şeklinde bir araya getiriyor, oturan pattern’leri ve bana zaman kaybettiren tuzakları vurguluyor. Effect düşünüyorsan, umarım bu rehber benim geçtiğim keşif sürecinin bir kısmından seni kurtarır.

Örtük Hataların Gizli 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 (~15KB compressed), ancak fiber runtime dahil olduğu için initial bundle fp-ts’ten daha büyük. Önemli olan şu: Effect birden fazla dependency’nin yerini alabilir:

  • Zod (~15KB) → @effect/schema
  • RxJS (~30KB) → Effect streams
  • Lodash utilities (~20KB) → Effect standard library
  • Custom DI framework (~10KB) → Layer system
  • Retry kütüphaneleri → Effect.retry
  • Promise utilities → Effect.promise, Effect.all

Net etki: kabaca neutral veya biraz daha büyük, ama dependency’leri birleştiriyorsun ve compile-time garantiler kazanıyorsun.

Öğrenme Yolu: 12 Haftalık Roadmap

Production ortamlarında işe yarayan pratik bir öğrenme yaklaşımı:

Hafta 1-2: Temel

Hafta 3-4: Servisler

Hafta 5-8: İleri Desenler

Hafta 9-12: Production

Effect temelleri

Hata yonetimi

Effect.gen

Context & Tag

Layer sistemi

Config modulu

Concurrency

Streams

Resource management

AWS Lambda

Test desenleri

Observability

Faz 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
)

Faz 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) => 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: "[email protected]" })
  yield* emailService.send(user.email, "Hoş geldin!", "Kaydolduğun için teşekkürler")

  return user
})

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

Faz 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 }
  })
)

Faz 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) => {
            yield* Logger.logWarn("Sipariş bulunamadı, atlanıyor", error)
            return Effect.unit
          },
          ProcessingFailed: (error) => {
            yield* Logger.logError("İşleme başarısız, DLQ'ya gönderiliyor", error)
            // Dead letter queue'ya gönder
            return Effect.unit
          }
        })
      )
    }

    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)

import { Schema } from "@effect/schema"

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 zorla.

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 faydalан:

// 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 mükemmel test altyapısı sağlıyor:

import { Effect, TestClock, 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 vs Alternatifler

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
  • Sıkı bundle size kısıtlamaları (<50KB total)
  • 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

Önemli Çıkarımlar

Effect ile çalışmak hata yönetimi ve API tasarımı hakkında düşünme şeklimi değiştirdi:

  1. Hataları gizleme, tip sistemine yaz: Effect<A, E, R> neyin yanlış gidebileceğini belgelemeye zorluyor. Başta fazla kod gibi hissettiriyor ama bug’ları production’da değil development’ta yakalıyor.

  2. Öğrenme eğrisi gerçek: Temel için 2-4 hafta, production-ready pattern’ler için 8-12 hafta planla. Yatırım, azalan debugging süresiyle geri dönüyor.

  3. Bundle size bir trade-off: Core küçük (~15KB), ama birden fazla dependency’yi birleştiriyorsun. Toplam bundle size’ı takip et.

  4. Test altyapısı mükemmel: TestClock, TestRandom ve mock layer’lar test’leri deterministik ve hızlı yapıyor.

  5. AWS Lambda entegrasyonu iyi çalışıyor: @effect-aws/lambda, doğru cleanup ile serverless uygulamalar için temiz pattern’ler sağlıyor.

  6. Kademeli benimseme mümkün: Effect’i aşamalı olarak sokabilirsin. Yeni özellikler veya yüksek karmaşıklık modüllerle başla.

  7. Her şeyin Effect’e ihtiyacı yok: Karmaşıklık abstraction’ı haklı çıkardığı yerde kullan. Basit fonksiyonlar basit kalabilir.

  8. Service-first mimari yardımcı oluyor: Implementasyonlardan önce interface’leri tanımlamak test edilebilirliği artırıyor ve implementasyon değişimini mümkün kılıyor.

  9. Error-first API tasarımı: Hata durumlarını baştan düşün. Tip sistemi bunları handle etmeni garantiliyor.

  10. Production-ready: Effect, Vercel’deki mühendisler dahil şirketlerde production’da kullanılıyor. Ekosistem olgun ve aktif olarak sürdürülüyor.

Başlangıç Kaynakları

Effect, TypeScript geliştirmede önemli bir evrim temsil ediyor. Her proje için değil, ama karmaşıklık haklı çıkardığında, Effect tüm bug kategorilerini yakalayan compile-time garantiler sağlıyor. Öğrenmeye yapılan ilk yatırım, azalan debugging süresi ve daha bakılabilir kod ile geri dönüyor.

İlgili yazılar

Type-Safe Lambda Middleware: Middy, Zod ve Builder Pattern ile Enterprise Uygulamalar

Middy builder pattern, Zod validation, feature flags ve secrets management kullanarak enterprise serverless uygulamaları için sürdürülebilir, type-safe Lambda middleware nasıl inşa edilir öğren.

aws-lambdamiddymiddleware+8
AWS Lambda Middleware ile Middy - Temiz Kod ve En İyi Uygulamalar

Middy'nin middleware kalıplarıyla Lambda geliştirmesini nasıl dönüştürdüğünü, tekrarlayan şablonlardan temiz, sürdürülebilir serverless fonksiyonlara geçişi keşfedin

aws-lambdamiddymiddleware+6
TypeScript Geliştiricilerin Monolitten Lambda'ya Taşıdığı Beş Anti-Pattern

DI container'lar, monolitik SDK'lar, god-handler'lar, modül üstü secret çağrıları ve ağır ORM'ler - soğuk başlatmada bedeli ve yerine geçen fonksiyonel yapı.

aws-lambdatypescriptserverless+2
TypeScript AI SDK Karşılaştırması: Agent Geliştirme için Vercel AI SDK vs OpenAI Agents SDK

AI agent geliştirmek için TypeScript SDK'larının pratik karşılaştırması - Vercel AI SDK, OpenAI Agents SDK ve AWS Bedrock entegrasyonu. Kod örnekleri, karar frameworkleri ve production patternleri içeriyor.

typescriptai-toolsserverless+4
AWS Lambda'da Bun ve Alternatif JavaScript Runtime'ları Çalıştırma

Bun ve Deno'yu AWS Lambda üzerinde custom runtime kullanarak çalıştırmak için teknik implementasyon rehberi, gerçek performans benchmark'ları, maliyet analizi ve production deployment pattern'leri ile.

aws-lambdabundeno+4