본문 바로가기
Backend2026년 5월 16일5분 읽기

Effect-TS 3.0 — TypeScript에서 진짜 함수형 만들기

YS
김영삼
조회 937
Effect-TS 3.0 — TypeScript에서 진짜 함수형 만들기

핵심 요약

Effect-TS 3.0이 안정화되며 한국 스타트업에도 도입 사례가 빠르게 늘었다. 핵심 가치는 세 가지 — 에러를 타입으로 추적, 의존성 주입을 정적으로 표현, 동시성/취소를 일관된 모델로 처리. 6개월 운영 결과 버그 리포트 31% 감소. 단, 팀 학습곡선 2~3주는 현실이다.

1. Promise vs Effect

Promise는 에러 타입이 unknown이고 취소가 없다. Effect는 Effect<A, E, R> — 성공 A, 실패 E, 요구 환경 R을 타입 시그니처에 박는다.

import { Effect, pipe } from "effect"

const fetchUser = (id: string): Effect.Effect<User, NotFoundError | DbError, Db> =>
  pipe(
    Db.query("SELECT * FROM users WHERE id = $1", [id]),
    Effect.flatMap((rows) =>
      rows.length === 0
        ? Effect.fail(new NotFoundError(id))
        : Effect.succeed(rows[0] as User)
    )
  )

호출자는 NotFoundError와 DbError를 둘 다 다뤄야 컴파일된다. catch 안 한 에러로 500 떨어뜨릴 일이 사라진다.

2. Layer — 정적 DI

class Db extends Effect.Service<Db>()("Db", {
  effect: Effect.gen(function*() {
    const pool = yield* Config.string("DATABASE_URL").pipe(
      Effect.flatMap(createPool)
    )
    return { query: (sql, params) => runQuery(pool, sql, params) }
  }),
}) {}

// 테스트
const TestDb = Layer.succeed(Db, {
  query: () => Effect.succeed([{ id: "1", name: "test" }]),
})

NestJS의 데코레이터 DI 없이 타입으로 모든 의존성이 드러난다. 테스트에서 모킹이 한 줄.

3. 에러 처리 — tagged union

class NotFoundError extends Data.TaggedError("NotFoundError")<{
  id: string
}> {}

const handled = pipe(
  fetchUser(id),
  Effect.catchTags({
    NotFoundError: (e) => Effect.succeed(defaultUser),
    DbError: (e) => Effect.fail(new ServerError(e)),
  })
)

4. 동시성 — 취소가 무료

const results = yield* Effect.forEach(ids, fetchUser, {
  concurrency: 10,
})

// 5초 안에 안 끝나면 전부 취소
const withTimeout = pipe(results, Effect.timeout("5 seconds"))

Promise.all + AbortController + setTimeout 조합으로 짜던 코드가 한 줄. 파이버 모델이라 취소가 진짜로 전파된다.

5. 실측 — 런타임 오버헤드

워크로드PromiseEffect 3.0차이
단순 비동기 1만 호출142ms198ms+39%
DB+검증 파이프라인 RPS4,8204,510-6.4%
p99 레이턴시(ms)3841+8%
번들 크기(서버 시작)0KB+82KB-

마이크로 벤치에선 차이가 크지만 실제 I/O 위주 서비스에선 한 자릿수 %. 트레이드오프로 받아들일 만하다.

6. 도입 후 실제 변한 것

  • try/catch 사라짐: 코드베이스 grep 결과 90% 감소
  • unhandled rejection 0건: 6개월간 단 1건도 안 잡힘
  • 의존성 누락 컴파일 에러: PR 머지 전에 잡힘

7. 함정

  • Effect.gen 안에서 yield* 빼먹기: 타입은 통과하지만 실행 안 됨
  • Promise와 섞기: Effect.tryPromise 없이 await 쓰면 컨텍스트 깨짐
  • Layer 순환 의존: 컴파일은 되는데 런타임에 멈춤

8. 도입 판단

  • 좋음: 신규 백엔드, 복잡한 비즈니스 로직, 에러가 도메인의 일부인 도메인
  • 유보: 기존 NestJS/Express 대규모 코드베이스에 부분 도입
  • 나쁨: FP 경험 0인 팀의 즉시 전면 도입

참고

  • effect.website
  • github.com/Effect-TS/effect

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록