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:
- Structured Concurrency: Otomatik cancellation ve resource cleanup ile built-in fiber runtime
- Service Management: Dependency injection için Context, Tag ve Layer sistemi
- Built-in Utilities: Clock, Random, Console, Logger servisleri dahil
- Error Merging: Farklı hatalarla effect’leri birleştirirken otomatik union types
- Testing Infrastructure: Deterministik testler için TestClock, TestRandom, TestContext
- Observability: OpenTelemetry desteğiyle native metrics, tracing, logging
- Streams: RxJS’e benzer ama doğru resource management ile
- 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
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:
- Layer sistemi runtime overhead olmadan temiz DI sağlıyor
- acquireRelease, Lambda shutdown’da finalizer’ların çalışmasını garantiliyor
- Structured logging için AWS Powertools entegrasyonu
- Tip güvenliği configuration error’ları compile time’da yakalıyor
- 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:
-
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.
-
Öğ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.
-
Bundle size bir trade-off: Core küçük (~15KB), ama birden fazla dependency’yi birleştiriyorsun. Toplam bundle size’ı takip et.
-
Test altyapısı mükemmel: TestClock, TestRandom ve mock layer’lar test’leri deterministik ve hızlı yapıyor.
-
AWS Lambda entegrasyonu iyi çalışıyor: @effect-aws/lambda, doğru cleanup ile serverless uygulamalar için temiz pattern’ler sağlıyor.
-
Kademeli benimseme mümkün: Effect’i aşamalı olarak sokabilirsin. Yeni özellikler veya yüksek karmaşıklık modüllerle başla.
-
Her şeyin Effect’e ihtiyacı yok: Karmaşıklık abstraction’ı haklı çıkardığı yerde kullan. Basit fonksiyonlar basit kalabilir.
-
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.
-
Error-first API tasarımı: Hata durumlarını baştan düşün. Tip sistemi bunları handle etmeni garantiliyor.
-
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ı
- Resmi Dokümantasyon: effect.website/docs/quickstart
- Effect GitHub: github.com/Effect-TS/effect
- Kapsamlı Giriş: Sandro Maglione’nin rehberi
- Migration Hikayesi: Inato’nun fp-ts’ten Effect’e geçişi
- Effect vs fp-ts: Resmi karşılaştırma
- AWS Lambda Paketi: @effect-aws/lambda
- Topluluk: 5,000+ üyeli aktif Discord
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
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.
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
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ı.
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.
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.