Skip to content
~/sph.sh

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:

typescript
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:

typescript
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ı:

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

typescript
import { Effect } from "effect"
// Geleneksel Promise tabanlı kodasync function validateEmail(email: string): Promise<string> {  if (!email.includes("@")) {    throw new Error("Invalid email")  }  return email.toLowerCase()}
// Typed error'larla Effect tabanlı kodimport { 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):

typescript
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

typescript
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

typescript
import { Context, Effect, Layer } from "effect"
// 1. Servis interface'ini tanımlainterface 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 kullanconst 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

typescript
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

typescript
// 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 etconst AppLayer = Layer.mergeAll(  DatabaseServiceLive,  UserServiceLive,  EmailServiceLive)
// Programı tüm dependency'lerle çalıştırconst 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

typescript
// 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 concurrencyconst processItems = (items: Item[]) =>  Effect.all(    items.map(processItem),    { concurrency: 10 } // Aynı anda 10 tane işle  )

Retry ve Timeout Stratejileri

typescript
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

typescript
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ımconst 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

typescript
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

typescript
import { EffectHandler, makeLambda } from "@effect-aws/lambda"import * as Logger from "@effect-aws/powertools-logger"import { Context, Effect, Layer, Data } from "effect"
// Servisi tanımlainterface OrderService {  process: (orderId: string) => Effect.Effect<Order, OrderError>}
const OrderService = Context.GenericTag<OrderService>("OrderService")
// Error tiplericlass OrderNotFound extends Data.TaggedError("OrderNotFound")<{  orderId: string}> {}
class ProcessingFailed extends Data.TaggedError("ProcessingFailed")<{  orderId: string  reason: string}> {}
type OrderError = OrderNotFound | ProcessingFailed
// Servis implementasyonuconst 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 handlerconst 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 etconst LambdaLayer = Layer.mergeAll(  OrderServiceLive,  DynamoDBServiceLive,  SNSServiceLive,  Logger.DefaultPowerToolsLoggerLayer)
export const handler = makeLambda(processOrderHandler, LambdaLayer)

Schema Validation (Zod'un Yerini Alıyor)

typescript
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:

typescript
// ❌ Yanlış - yield* eksikconst 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ğruconst 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:

typescript
// ❌ Basit synchronous fonksiyon için gereksizconst addNumbers = (a: number, b: number): Effect.Effect<number> =>  Effect.succeed(a + b)
// ✅ Daha iyi - basit tutconst addNumbers = (a: number, b: number): number => a + b
// ✅ Hata durumları varsa Effect kullanconst 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:

typescript
// ❌ Karışık kullanımconst 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:

typescript
// ✅ E tipinde sadece expected error'larconst 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ан:

typescript
// ❌ 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 concurrentconst 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:

typescript
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 testconst 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:

typescript
// Büyük dependency'ler için dynamic import kullanconst heavyOperation = Effect.gen(function* () {  const lib = yield* Effect.promise(() => import("heavy-lib"))  return lib.process()})
// Lazy servis initializationconst 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
typescript
// 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.

typescript
// Yeni özellik: Effect ile ödeme işlemeconst 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:

typescript
// Mevcut servis (olduğu gibi kal)interface LegacyUserService {  getUser(id: string): Promise<User>}
// Effect wrapperconst 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:

typescript
// 1. Interface'i tanımlainterface PaymentService {  processPayment: (amount: number) => Effect.Effect<Receipt, PaymentError>}
// 2. Tag oluşturconst PaymentService = Context.GenericTag<PaymentService>("PaymentService")
// 3. Birden fazla implementasyonconst StripePaymentServiceLive = Layer.effect(/* ... */)const MockPaymentServiceLive = Layer.succeed(/* ... */)
// 4. Business logic implementasyondan bağımsızconst 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