Từ Logic Tạm Bợ Đến Durable Execution
Ai trong chúng ta cũng đã từng trải qua cảnh đó. Quản lý một tiến trình chạy dài trong hệ thống phân tán thường bắt đầu bằng một cái setTimeout đơn giản hoặc một cron job cơ bản. Có thể bạn dùng BullMQ để xử lý tác vụ nền. Nhưng khi business logic ngày càng phức tạp hơn — như một quy trình onboarding người dùng kéo dài 30 ngày hay một pipeline thanh toán nhiều rủi ro — các giải pháp tự chế này bắt đầu bộc lộ điểm yếu. Bạn sẽ phát hiện mình đang viết “glue code” cho retry và lưu trạng thái nhiều hơn là viết tính năng thực sự.
Cách Tiếp Cận Truyền Thống vs. Durable Execution
Trong môi trường Node.js thông thường, server crash đồng nghĩa với việc mọi state trong bộ nhớ bị xóa sạch. Nếu bạn chưa lưu từng mốc tiến độ vào database, tiến trình đó coi như mất trắng. Nếu một API bên ngoài như Stripe hay Twilio bị sập, bạn phải tự viết logic exponential backoff và theo dõi dead letter queue chỉ để giữ hệ thống không chết.
Temporal lật ngược tình thế với Durable Execution. Thay vì bạn phải tự quản lý state, Temporal ghi lại từng bước code của bạn thực thi. Nếu worker process bị crash, Temporal đơn giản là tiếp tục chạy nó trên một máy khác. Toàn bộ biến cục bộ và call stack được khôi phục đúng tại điểm dừng. Hãy nghĩ đến tính năng “lưu game” nhưng áp dụng cho toàn bộ kiến trúc backend của bạn.
| Tính năng | Truyền thống (Queue + DB) | Cách của Temporal |
|---|---|---|
| Theo dõi State | Tự viết câu UPDATE ở mỗi bước |
Tự động và minh bạch |
| Retry | Vòng lặp tự code, dễ vỡ | Khai báo policy, robust |
| Timeout | Ác mộng khi theo dõi qua nhiều tuần | Hỗ trợ sleep hàng tháng ngay từ đầu |
| Khả năng quan sát | Phải tự xây dashboard quản trị | UI có sẵn với toàn bộ lịch sử thực thi |
Thực Tế Khi Áp Dụng Temporal
Temporal không chỉ là một thư viện khác; đây là sự thay đổi tư duy căn bản trong cách bạn viết code. Dù nó giải quyết được nhiều vấn đề đau đầu, nhưng cũng đi kèm những quy tắc riêng mà team bạn cần nắm vững.
Lý Do Bạn Sẽ Thích Nó
- Độ Tin Cậy Vượt Trội: Workflow của bạn gần như miễn nhiễm với các lỗi nhất thời. Nếu một worker chết, worker khác tiếp tục công việc mà không mất một mili-giây tiến độ nào.
- Code Tuyến Tính: Bạn có thể viết code trông như một script đơn giản. Không cần phải tự hỏi “Điều gì xảy ra nếu mất điện giữa dòng 10 và dòng 11?”
- Du Hành Thời Gian: Môi trường test cho phép bạn mock thời gian. Bạn có thể kiểm tra một chu kỳ thanh toán 30 ngày trong chưa đến 200 mili-giây.
Những Đánh Đổi Phải Chấp Nhận
- Chi Phí Hạ Tầng: Bạn cần quản lý một Temporal Cluster, bao gồm một database (Postgres hoặc Cassandra) và một engine đánh index như Elasticsearch.
- Quy Tắc Determinism: Đây là điều quan trọng nhất. Code trong Workflow phải mang tính deterministic. Bạn không được dùng
Math.random(),new Date(), hay gọifetch()trực tiếp bên trong Workflow. Những thứ này phải nằm trong “Activities.” - Đường Cong Học Tập: Dự kiến team bạn cần 1-2 tuần để hiểu rõ sự phân tách giữa orchestration (Workflows) và thực thi (Activities).
Kiến Trúc Sẵn Sàng Cho Production
Với một hệ thống xử lý hàng nghìn workflow đồng thời, tôi khuyến nghị cấu trúc tách biệt rõ ràng. Tách riêng phần trigger khỏi phần thực thi đảm bảo API của bạn vẫn phản hồi nhanh dù tải nặng đến đâu.
- Temporal Cluster: Bộ não của toàn bộ hệ thống. Bạn có thể tự host qua Docker hoặc giao việc bảo trì cho Temporal Cloud.
- Worker: Một Node.js process chuyên dụng. Nó không xử lý HTTP request; nhiệm vụ duy nhất là poll Temporal Server để lấy task và thực thi chúng.
- Client: API Express hoặc Fastify hiện tại của bạn. Nhiệm vụ duy nhất của nó là nói với Temporal: “Này, hãy khởi động workflow này cho User X.”
TypeScript ở đây là bắt buộc. Node.js SDK sử dụng type mapping nâng cao để đảm bảo Client và Worker luôn đồng bộ, phát hiện bug tiềm ẩn trước khi chúng lên production.
Thực Hành: Xây Dựng Subscription Engine
Hãy xem một tình huống thực tế. Chúng ta cần tính phí khách hàng $29.99 và gửi email chào mừng. Nếu thanh toán thất bại do lỗi mạng, chúng ta muốn retry. Nếu thất bại năm lần, chúng ta leo thang lên cho con người xử lý.
Bước 1: Code Các Activity
Activity là những worker trong hệ thống của bạn. Chúng xử lý thế giới thực lộn xộn ngoài kia, như gọi API và ghi database.
// activities.ts
export async function processPayment(amount: number): Promise<string> {
// Trong ứng dụng thật, đây sẽ gọi Stripe SDK
if (Math.random() < 0.1) throw new Error("Upstream Gateway Timeout");
return "CHARGED_SUCCESSFULLY";
}
export async function sendWelcomeEmail(email: string): Promise<void> {
console.log(`Đã gửi email đến ${email}`);
}
Bước 2: Orchestrate Workflow
Đây là nơi phép màu xảy ra. Hãy chú ý logic rất rõ ràng và tuần tự, dù bên dưới xử lý retry logic phức tạp.
// workflows.ts
import { proxyActivities, sleep } from '@temporalio/workflow';
import type * as activities from './activities';
const { processPayment, sendWelcomeEmail } = proxyActivities<typeof activities>({
startToCloseTimeout: '1 minute',
retry: {
initialInterval: '2s',
maximumAttempts: 5,
backoffCoefficient: 2,
},
});
export async function subscriptionWorkflow(email: string, amount: number): Promise<void> {
const status = await processPayment(amount);
if (status === 'CHARGED_SUCCESSFULLY') {
await sendWelcomeEmail(email);
}
// Sleep này có thể kéo dài 30 ngày.
// Worker có thể restart 100 lần, Temporal vẫn không quên timer này.
await sleep('30 days');
}
Bước 3: Khởi Động Worker
Worker kết nối đến cluster và chờ việc. Đây là phòng máy của hệ thống phân tán của bạn.
// worker.ts
import { Worker } from '@temporalio/worker';
import * as activities from './activities';
async function run() {
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
activities,
taskQueue: 'billing-v1',
});
await worker.run();
}
run().catch((err) => {
console.error("Worker bị crash:", err);
process.exit(1);
});
Bài Học Xương Máu Từ Production
Sau khi triển khai Temporal trên nhiều dự án có lưu lượng cao, bốn lời khuyên sau đây sẽ giúp bạn tiết kiệm hàng giờ debug.
1. Tôn Trọng Versioning
Workflow tồn tại lâu dài. Nếu bạn thay đổi code trong workflows.ts trong khi một tiến trình 30 ngày đang chạy, phần “replay” sẽ thất bại vì lịch sử không còn khớp với code nữa. Luôn dùng API patch để đưa vào những thay đổi logic breaking một cách an toàn.
2. Idempotency Là Người Bạn Tốt Nhất Của Bạn
Một activity có thể chạy nhiều hơn một lần nếu worker chết đúng lúc task vừa hoàn thành. Luôn truyền idempotency key vào Stripe hoặc database để đảm bảo bạn không tính phí khách hàng hai lần cho cùng một hành động.
3. Tận Dụng Web UI
Temporal Web UI thực sự thay đổi cuộc chơi. Nó cung cấp timeline trực quan của từng sự kiện. Khi workflow bị kẹt, đừng chỉ đào bới trong log. Hãy mở UI để xem chính xác activity nào đang bị timeout và xem toàn bộ stack trace của lỗi.
4. Dùng Signal Cho Tương Tác
Workflow không chỉ là “bắn rồi quên”. Dùng Signal để xử lý các sự kiện bên ngoài, như người dùng click “Hủy Đăng Ký” giữa tháng. Điều này cho phép workflow phản ứng với thế giới thực mà không mất trạng thái nội bộ.
Xây dựng với Temporal đòi hỏi bạn chuyển từ tư duy “làm sao bắt được lỗi này?” sang “tiến trình này nên phát triển như thế nào?”. Bằng cách giao phó gánh nặng quản lý state cho Temporal, bạn có thể tập trung vào business logic thực sự tạo ra giá trị.

