+1

Effect: Thư viện đang cố gắng trở thành “Standard Library” cho TypeScript

Nếu bạn đã từng build một backend TypeScript đủ lớn, rất có thể bạn đã gặp những vấn đề như:

  • Async function throw lỗi nhưng không biết trước sẽ fail kiểu gì
  • Retry API phải tự viết
  • Timeout phải tự viết
  • Dependency Injection ngày càng phức tạp
  • Logging, tracing, observability bị phân tán khắp nơi
  • Test khó vì service phụ thuộc chồng chéo

Đây chính là nhóm vấn đề mà Effect muốn giải quyết.

Website chính thức:


image.png

Effect là gì?

Effect là một framework/thư viện TypeScript theo hướng Functional Programming (FP), được thiết kế để xây dựng các ứng dụng production-grade với:

  • Typed Error Handling
  • Dependency Injection
  • Structured Concurrency
  • Resource Management
  • Retry Policies
  • Observability
  • Streams
  • Workflows

Theo mô tả từ đội ngũ phát triển:

Effect là một hệ sinh thái công cụ để xây dựng ứng dụng TypeScript mạnh mẽ và an toàn hơn.

Thay vì chỉ là một thư viện utility, Effect cố gắng trở thành một lớp abstraction cho toàn bộ side effects trong ứng dụng.


Vấn đề của Promise và Async/Await

TypeScript hiện tại chủ yếu xử lý async bằng Promise.

Ví dụ:

async function getUser(id: string) {
  const response = await fetch(`/users/${id}`)

  if (!response.ok) {
    throw new Error("User not found")
  }

  return response.json()
}

Nhìn có vẻ ổn.

Nhưng khi project lớn lên:

  • Function có thể throw nhiều loại lỗi khác nhau
  • Caller không biết phải handle lỗi gì
  • Dependency bị ẩn
  • Retry, timeout, cancellation phải tự xử lý

Compiler gần như không giúp được nhiều.


Khái niệm cốt lõi: Effect<A, E, R>

Trung tâm của thư viện là type:

Effect<A, E, R>

Ý nghĩa:

Type Vai trò
A Giá trị thành công
E Kiểu lỗi
R Dependency cần có

Ví dụ:

Effect<User, UserNotFound, UserRepository>

Có thể hiểu là:

Trả về User
Có thể lỗi UserNotFound
Cần UserRepository

Điểm quan trọng là compiler biết toàn bộ điều này.


Typed Errors: Error Handling có type

Trong JavaScript:

try {
  await fetch(...)
} catch (err) {
  // any
}

Vấn đề:

err = unknown

hoặc:

err = any

Bạn không biết chính xác function có thể fail theo cách nào.

Effect biến lỗi thành một phần của type.

Ví dụ:

Effect<User, NetworkError, never>

Compiler sẽ biết:

Có thể fail bằng NetworkError

Tư duy này khá giống:

Result<User, NetworkError>

trong Rust.


Dependency Injection bằng Type

NestJS thường viết:

@Injectable()
class UserService {
  constructor(
    private readonly repo: UserRepo
  ) {}
}

Effect lại encode dependency vào type:

Effect<User, Error, UserRepo>

Điều này giúp:

  • Dependency rõ ràng hơn
  • Không cần decorators
  • Không cần reflection
  • Dễ test hơn

Retry và Resilience

Một trong những use case phổ biến nhất:

fetch()

có thể fail vì:

  • Network
  • Timeout
  • Rate limit
  • Temporary outage

Thông thường:

for (...) {
  try {
    ...
  } catch {}
}

Effect có retry policy built-in:

Effect.retry(...)

Ví dụ:

1s
2s
4s
8s

theo exponential backoff.


Structured Concurrency

Đây là feature rất mạnh của Effect.

Ví dụ:

const users = fetchUsers()
const posts = fetchPosts()
const comments = fetchComments()

Chạy song song:

Effect.all([
  users,
  posts,
  comments
])

Nếu một task fail:

posts fail

các task còn lại có thể bị cancel và cleanup tự động.

Cách hoạt động này tương tự:

  • Kotlin Coroutines
  • Go Context
  • Rust Tokio
  • Scala ZIO

Observability Built-in

Các hệ thống production thường cần:

  • Logging
  • Tracing
  • Metrics
  • OpenTelemetry

Thông thường phải ghép nhiều thư viện lại.

Effect được thiết kế để hỗ trợ observability ngay từ runtime.


Resource Management

Một lỗi rất phổ biến:

const connection = await db.connect()

try {
  ...
} finally {
  connection.close()
}

Nếu code phức tạp hơn:

Database
Redis
Queue
File
Stream

việc cleanup trở nên khó kiểm soát.

Effect có hệ thống Scope và Resource Management để tự động quản lý vòng đời resource.


Hệ sinh thái của Effect

Hiện monorepo có khá nhiều package:

Package Chức năng
effect Core
@effect/ai AI workflows
@effect/ai-openai OpenAI integration
@effect/cli CLI apps
@effect/cluster Distributed systems

Nguồn:


Use Cases thực tế

Backend APIs

Rất phù hợp với:

  • Node.js
  • Express
  • Fastify
  • Hono
  • NestJS

Đặc biệt khi:

Request
 ↓
Validate
 ↓
Database
 ↓
Cache
 ↓
Queue
 ↓
Response

có nhiều async operations.


Microservices

Effect đặc biệt mạnh ở đây.

Ví dụ:

Order Service
Inventory Service
Payment Service
Notification Service

Bạn thường cần:

  • Retry
  • Timeout
  • Circuit Breaker
  • Queue
  • Tracing

Effect được thiết kế cho các workflow như vậy.


AI Agents

Đây là use case đang nổi lên gần đây.

Ví dụ:

OpenAI
 ↓
RAG
 ↓
Database
 ↓
Tool Call
 ↓
Another Model

Mỗi bước đều có thể fail.

Effect giúp:

  • Retry
  • Timeout
  • Fallback
  • Tracing

dễ quản lý hơn Promise thông thường.


Data Pipelines

Ví dụ:

Kafka
 ↓
Transform
 ↓
Postgres
 ↓
Redis

Effect cung cấp:

  • Stream
  • Queue
  • PubSub

để xây dựng pipeline xử lý dữ liệu.


Những điểm trừ

Không có công cụ nào hoàn hảo.


Learning Curve cao

Đây là nhược điểm lớn nhất.

Nhiều developer quen:

await getUser()

sẽ thấy khó tiếp cận với:

pipe(
  Effect.map(...),
  Effect.flatMap(...)
)

hoặc:

Effect.gen(...)

Cộng đồng cũng thường xuyên nhắc đến learning curve khá nặng của Effect.

Nguồn:


Không phải thư viện "thêm vào cho vui"

Một nhận xét khá phổ biến từ cộng đồng:

Effect không phải thứ bạn "thêm" vào project. Nó là thứ bạn xây kiến trúc quanh nó.

Nghĩa là:

Dùng Effect = thay đổi cách viết ứng dụng

chứ không chỉ thêm package.

Nguồn:


Overkill cho CRUD thông thường

Nếu bạn đang làm:

  • Admin Dashboard
  • CMS
  • Blog
  • Ecommerce nhỏ
  • SaaS CRUD

thì stack như:

Next.js
Prisma
Zod
TanStack Query

thường đã đủ.

Effect có thể làm tăng độ phức tạp không cần thiết.


Khi nào nên dùng Effect?

Nên cân nhắc khi project có:

  • Microservices
  • Event-driven architecture
  • AI Agents
  • Background Jobs
  • Workflow Engine
  • Queue Processing
  • Distributed Systems
  • Financial Systems
  • Nhiều retry / timeout / fallback logic

Khi nào không nên dùng?

Không nên nếu:

  • MVP cần ship nhanh
  • CRUD application đơn giản
  • Team chưa quen FP
  • Chỉ muốn typed errors

Trong trường hợp chỉ cần typed errors, nhiều developer chọn:

  • neverthrow
  • true-myth

vì nhẹ hơn rất nhiều.

Nguồn:


Một góc nhìn thú vị

Nhiều người mô tả Effect là:

"Standard Library mà TypeScript đáng lẽ nên có."

Bởi vì nó gom rất nhiều thứ vốn đang bị phân tán:

  • Error Handling
  • Dependency Injection
  • Retry
  • Concurrency
  • Streams
  • Resource Management
  • Observability

vào cùng một hệ thống thống nhất.

Nguồn:


Kết luận

Effect không phải thư viện dành cho mọi dự án.

Nếu ứng dụng của bạn chủ yếu là CRUD thông thường, chi phí học và áp dụng có thể lớn hơn lợi ích nhận được.

Nhưng nếu bạn đang xây:

  • AI Agents
  • Workflow Systems
  • Event-driven Architecture
  • Microservices
  • Backend phức tạp với nhiều async operations

thì Effect là một trong những thư viện mạnh nhất hiện nay trong hệ sinh thái TypeScript.

Nó không chỉ giải quyết async programming, mà còn cố gắng cung cấp một mô hình nhất quán cho toàn bộ side effects trong ứng dụng.


Tài liệu tham khảo

Chính thức

Học Effect

Bài phân tích


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí