Serverless & FaaS Architecture: Build Mà Không Cần Quản Lý Server

Mọi architecture pattern chúng ta đã đề cập đều giả định bạn đang quản lý server — dù là monolith trên một máy, microservices trên cluster, hay container được Kubernetes orchestrate. Bạn provision capacity, xử lý scaling, patch hệ điều hành, và trả tiền cho idle time.
Serverless architecture loại bỏ gánh nặng operation đó. Bạn viết function, deploy chúng, và cloud provider xử lý mọi thứ còn lại: provisioning, scaling, patching, và billing. Bạn chỉ trả tiền cho những gì sử dụng — per request, per millisecond execution.
Nhưng "serverless" không có nghĩa là "không có server." Nó có nghĩa là bạn không cần nghĩ về server. Server vẫn tồn tại — chúng chỉ là problem của người khác.
Trong bài viết này, chúng ta sẽ đề cập:
✅ Serverless thực sự là gì (FaaS + BaaS)
✅ FaaS hoạt động thế nào: AWS Lambda, Azure Functions, Google Cloud Functions, Vercel
✅ Cold start và ảnh hưởng đến performance
✅ Stateless function design
✅ Event trigger: HTTP, queue, schedule, storage
✅ Serverless database: Neon, PlanetScale, DynamoDB
✅ Composition pattern: chaining, fan-out/fan-in, orchestration
✅ Cost model: pay-per-invocation vs always-on
✅ Limitation: execution time, memory, vendor lock-in
✅ Serverless framework: SST, Serverless Framework, SAM
✅ Khi nào serverless phù hợp vs server truyền thống
✅ Hybrid architecture
Serverless Là Gì?
Serverless là thuật ngữ bao quát cho hai danh mục:
Function-as-a-Service (FaaS)
FaaS cho phép bạn deploy từng function riêng lẻ chạy để phản hồi event. Platform quản lý runtime, scaling, và infrastructure.
Ví dụ: AWS Lambda, Azure Functions, Google Cloud Functions, Vercel Serverless Functions, Cloudflare Workers.
Backend-as-a-Service (BaaS)
BaaS cung cấp backend service được quản lý hoàn toàn mà bạn consume qua API thay vì build từ đầu.
Ví dụ:
- Authentication: Auth0, Clerk, Firebase Auth, Neon Auth
- Database: Neon (Postgres), PlanetScale (MySQL), DynamoDB, Supabase
- Storage: S3, Cloudflare R2, Supabase Storage
- Real-time: Pusher, Ably, Supabase Realtime
- Email: Resend, SendGrid, SES
Ứng dụng serverless thường kết hợp FaaS (cho custom logic) với BaaS (cho managed infrastructure).
FaaS Hoạt Động Thế Nào
Lifecycle Của Một Function Invocation
Ví Dụ AWS Lambda
// handler.ts — Một AWS Lambda function đơn giản
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
// Initialization code — chạy MỘT LẦN per cold start
const dbClient = new DatabaseClient(process.env.DATABASE_URL!);
// Handler — chạy MỖI invocation
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
const { httpMethod, path, body } = event;
if (httpMethod === "GET" && path === "/api/users") {
const users = await dbClient.query("SELECT * FROM users LIMIT 50");
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(users),
};
}
if (httpMethod === "POST" && path === "/api/users") {
const { name, email } = JSON.parse(body || "{}");
const user = await dbClient.query(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *",
[name, email]
);
return {
statusCode: 201,
body: JSON.stringify(user),
};
}
return { statusCode: 404, body: "Not Found" };
};Vercel Serverless Functions (Next.js)
// app/api/users/route.ts — Next.js Route Handler (chạy như serverless)
import { NextRequest, NextResponse } from "next/server";
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const limit = searchParams.get("limit") || "50";
const users = await sql`SELECT * FROM users LIMIT ${limit}`;
return NextResponse.json(users);
}
export async function POST(request: NextRequest) {
const { name, email } = await request.json();
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
return NextResponse.json(user, { status: 201 });
}Azure Functions
// src/functions/getUsers.ts
import { app, HttpRequest, HttpResponseInit } from "@azure/functions";
app.http("getUsers", {
methods: ["GET"],
authLevel: "anonymous",
route: "users",
handler: async (request: HttpRequest): Promise<HttpResponseInit> => {
const users = await db.query("SELECT * FROM users");
return {
status: 200,
jsonBody: users,
};
},
});Cold Start: Cái Giá Của Serverless
Cold start xảy ra khi platform cần tạo container mới cho function. Nó bao gồm:
- Provisioning container/micro-VM
- Download code package
- Initialize runtime (Node.js, Python, Java, v.v.)
- Chạy initialization code (database connection, config loading)
So Sánh Cold Start
| Runtime | Cold Start (điển hình) | Ghi chú |
|---|---|---|
| Node.js | 100-500ms | Nhanh, runtime nhỏ |
| Python | 100-500ms | Nhanh, tương tự Node.js |
| Go | 50-200ms | Compiled binary, nhanh nhất |
| Rust | 50-200ms | Compiled binary, rất nhanh |
| Java | 1-10s | JVM startup, class loading |
| .NET | 500ms-3s | CLR initialization |
Chiến Lược Giảm Cold Start
1. Giữ function nhỏ
// ❌ Một function khổng lồ với nhiều dependency
import express from "express";
import mongoose from "mongoose";
import redis from "redis";
import { S3Client } from "@aws-sdk/client-s3";
import sharp from "sharp"; // Image processing — nặng!
// 50MB deployment package, cold start chậm
// ✅ Function focused với dependency tối thiểu
import { neon } from "@neondatabase/serverless"; // ~200KB
// 2MB deployment package, cold start nhanh2. Dùng provisioned concurrency (AWS Lambda)
# serverless.yml
functions:
api:
handler: handler.main
provisionedConcurrency: 5 # 5 instance luôn warm3. Initialize ngoài handler
// ✅ Database client tạo MỘT LẦN, reuse qua các invocation
const sql = neon(process.env.DATABASE_URL!);
export async function handler(event: any) {
// sql đã initialized trên warm start
const result = await sql`SELECT 1`;
return { statusCode: 200, body: "OK" };
}4. Chọn runtime nhanh cho function nhạy cảm latency
Go và Rust có cold start nhanh nhất. Với Node.js, dùng lightweight dependency và tree-shake code không dùng.
Stateless Function Design
Serverless function phải stateless — mỗi invocation là độc lập, không có shared memory giữa các lần gọi.
// ❌ SAI — Stateful: dựa vào in-memory state
let requestCount = 0; // Reset khi cold start, không shared giữa instance
export async function handler(event: any) {
requestCount++; // Không đáng tin! Instance khác nhau có count khác nhau
return { body: `Request #${requestCount}` };
}// ✅ ĐÚNG — Stateless: dùng external storage
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
});
export async function handler(event: any) {
const count = await redis.incr("request-count");
return { body: `Request #${count}` };
}Lưu state ở đâu:
| Loại state | Giải pháp | Ví dụ |
|---|---|---|
| Session data | Redis (Upstash), JWT token | User login session |
| Persistent data | Database (Neon, DynamoDB) | User profile, order |
| Cache | Redis (Upstash), CDN | API response, computed value |
| File upload | Object storage (S3, R2) | Image, document |
| Temporary state | Step Functions, workflow engine | Multi-step process |
Event Trigger
Serverless function phản hồi event — không chỉ HTTP request.
HTTP Trigger
Trigger phổ biến nhất — function hoạt động như API endpoint.
// Vercel: app/api/webhook/route.ts
export async function POST(request: NextRequest) {
const payload = await request.json();
await processWebhook(payload);
return NextResponse.json({ received: true });
}Queue Trigger
Process message từ queue một cách async.
// AWS Lambda: SQS trigger
export async function handler(event: SQSEvent) {
for (const record of event.Records) {
const message = JSON.parse(record.body);
await processOrder(message);
}
}Schedule Trigger (Cron)
Chạy function theo lịch — hoàn hảo cho periodic task.
// Vercel: app/api/cron/daily-report/route.ts
export async function GET(request: NextRequest) {
// Verify cron secret
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const report = await generateDailyReport();
await sendReportEmail(report);
return NextResponse.json({ success: true });
}// vercel.json
{
"crons": [
{
"path": "/api/cron/daily-report",
"schedule": "0 9 * * *"
}
]
}Storage Trigger
Phản ứng với file upload hoặc thay đổi trong object storage.
// AWS Lambda: S3 trigger — process uploaded image
export async function handler(event: S3Event) {
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = record.s3.object.key;
// Download, resize, và upload thumbnail
const image = await s3.getObject({ Bucket: bucket, Key: key });
const thumbnail = await sharp(image.Body).resize(200, 200).toBuffer();
await s3.putObject({
Bucket: bucket,
Key: `thumbnails/${key}`,
Body: thumbnail,
});
}
}Database Trigger
Phản ứng với database change — có sẵn trên một số platform.
// Supabase: Database webhook khi có user mới
// Trigger khi row được insert vào bảng "users"
export async function handler(event: any) {
const newUser = event.record;
await sendWelcomeEmail(newUser.email);
await createDefaultSettings(newUser.id);
}Serverless Database
Database truyền thống giả định long-lived connection từ vài server. Serverless function tạo nhiều short-lived connection từ nhiều instance, có thể áp đảo database truyền thống.
Connection Problem
100 Lambda invocation = 100 database connection. Scale lên 1000 invocation và database hết connection.
Giải Pháp
1. Database serverless-native
Database được thiết kế cho serverless workload xử lý connection pooling tự động.
// Neon — serverless Postgres với HTTP driver
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);
export async function handler() {
// HTTP-based query — không cần persistent connection
const users = await sql`SELECT * FROM users WHERE active = true`;
return { statusCode: 200, body: JSON.stringify(users) };
}2. Connection pooling
Dùng connection pooler giữa function và database.
// Neon với connection pooling
// Connection string dùng pooler endpoint
const sql = neon(process.env.DATABASE_URL!);
// DATABASE_URL=postgres://user:[email protected]/neondb3. DynamoDB — truly serverless NoSQL
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({});
export async function handler(event: any) {
const result = await client.send(
new GetItemCommand({
TableName: "Users",
Key: { userId: { S: event.pathParameters.id } },
})
);
return { statusCode: 200, body: JSON.stringify(result.Item) };
}So Sánh Serverless Database
| Database | Loại | Connection Model | Phù hợp cho |
|---|---|---|---|
| Neon | PostgreSQL | HTTP + pooling | Full SQL, branching, scale-to-zero |
| PlanetScale | MySQL | HTTP driver | MySQL workload, branching |
| Supabase | PostgreSQL | Pooling + REST | Full-stack với auth, real-time |
| DynamoDB | NoSQL | HTTP (native) | High-throughput key-value |
| Upstash Redis | Key-value | HTTP | Caching, rate limiting, queue |
| Turso | SQLite | HTTP | Edge-first, embedded |
Composition Pattern
Function riêng lẻ thì đơn giản. Thách thức là compose chúng thành workflow phức tạp.
1. Function Chaining
Một function gọi function khác tuần tự.
// Simple chaining — mỗi function gọi function tiếp theo
export async function validateOrder(order: Order) {
validateItems(order.items);
validateAddress(order.shippingAddress);
// Gọi function tiếp
await processPayment(order);
}
async function processPayment(order: Order) {
const payment = await stripe.charges.create({
amount: order.total,
currency: "usd",
});
await reserveInventory(order, payment.id);
}Vấn đề: Nếu reserveInventory fail, bạn đã charge khách hàng. Không có error handling hay retry built-in.
2. Fan-Out / Fan-In
Phân phối công việc qua parallel function, rồi aggregate kết quả.
// Fan-out: Chia công việc thành parallel task
export async function processLargeDataset(event: any) {
const records = await fetchRecords();
const batches = chunk(records, 100); // Chia thành batch 100
// Fan-out: invoke parallel Lambda function
const promises = batches.map((batch, index) =>
lambda.invoke({
FunctionName: "processBatch",
Payload: JSON.stringify({ batch, batchIndex: index }),
})
);
const results = await Promise.all(promises);
// Fan-in: aggregate kết quả
const totalProcessed = results.reduce(
(sum, r) => sum + JSON.parse(r.Payload!).processed,
0
);
return { totalProcessed };
}3. Orchestration (Step Functions / Durable Functions)
Cho workflow phức tạp, multi-step với error handling, retry, và human approval.
// AWS Step Functions state machine (đơn giản hóa)
{
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:validateOrder",
"Next": "ProcessPayment",
"Catch": [{ "ErrorEquals": ["ValidationError"], "Next": "OrderFailed" }]
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:processPayment",
"Retry": [{ "ErrorEquals": ["TimeoutError"], "MaxAttempts": 3 }],
"Next": "ReserveInventory",
"Catch": [{ "ErrorEquals": ["PaymentDeclined"], "Next": "OrderFailed" }]
},
"ReserveInventory": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:reserveInventory",
"Next": "SendConfirmation"
},
"SendConfirmation": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123:function:sendConfirmation",
"End": true
},
"OrderFailed": {
"Type": "Fail",
"Cause": "Order processing failed"
}
}
}Khi Nào Dùng Mỗi Pattern
| Pattern | Dùng khi | Tránh khi |
|---|---|---|
| Chaining | Bước tuần tự đơn giản, all-or-nothing | Cần error handling phức tạp |
| Fan-out/fan-in | Công việc parallel độc lập | Các bước depend vào nhau |
| Orchestration | Workflow phức tạp, retry, human approval, long-running | Simple request-response |
| Event-driven | Loose coupling, eventual consistency | Cần strict ordering |
Cost Model
Pricing serverless khác biệt cơ bản so với hosting truyền thống.
Pay-Per-Invocation
Server truyền thống:
1 EC2 t3.medium = $30/tháng (chạy 24/7, kể cả 2 giờ sáng với zero traffic)
Serverless (AWS Lambda):
Giá = (invocation × $0.20/triệu) + (duration × $0.0000166667/GB-second)
Ví dụ: 1 triệu request/tháng, trung bình 200ms, 256MB memory
= (1M × $0.20/M) + (1M × 0.2s × 0.25GB × $0.0000166667)
= $0.20 + $0.83
= $1.03/thángKhi Nào Serverless Rẻ Hơn
Serverless tiết kiệm tiền khi:
- Traffic spiky hoặc không dự đoán được
- Nhiều endpoint với traffic riêng lẻ thấp
- Development/staging environment (scale to zero)
- Background job chạy định kỳ
Server rẻ hơn khi:
- Traffic cao ổn định (>100M request/tháng)
- Long-running process (>15 phút)
- GPU workload hoặc computation nặng
- Load dự đoán được, ổn định
Chi Phí Ẩn
- Data transfer giữa các service (egress fee)
- API Gateway charge ($3.50/triệu request trên AWS)
- Step Functions state transition ($25/triệu transition)
- CloudWatch log và monitoring
- Provisioned concurrency để loại bỏ cold start
Limitation
Giới Hạn Execution Time
| Platform | Max Execution Time |
|---|---|
| AWS Lambda | 15 phút |
| Azure Functions | 10 phút (Consumption), unlimited (Premium) |
| Google Cloud Functions | 9 phút (1st gen), 60 phút (2nd gen) |
| Vercel Serverless | 10 giây (Hobby), 300 giây (Pro) |
| Cloudflare Workers | 30 giây (Bundled), 15 phút (Unbound) |
Giới Hạn Memory
| Platform | Memory Range |
|---|---|
| AWS Lambda | 128MB - 10GB |
| Azure Functions | 256MB - 14GB |
| Vercel Serverless | 1024MB - 3008MB |
| Cloudflare Workers | 128MB |
Giới Hạn Payload Size
| Platform | Request/Response Size |
|---|---|
| AWS Lambda | 6MB (sync), 256KB (async) |
| Azure Functions | 100MB |
| Vercel Serverless | 4.5MB request body |
| API Gateway | 10MB |
Vendor Lock-In
Mỗi platform có API, deployment model, và configuration riêng. Chuyển từ AWS Lambda sang Azure Functions đòi hỏi viết lại infrastructure code.
Chiến lược giảm thiểu:
- Giữ business logic trong pure function, tách biệt khỏi platform-specific handler
- Dùng abstraction layer (ví dụ Serverless Framework)
- Dùng standard protocol (HTTP, SQL) thay vì proprietary service khi có thể
- Containerize function cho portability (AWS Lambda support container image)
Serverless Framework
Framework đơn giản hóa deployment, configuration, và local development.
SST (Serverless Stack)
Framework hiện đại build trên AWS CDK. Support Next.js, Remix, Astro, và hơn thế nữa.
// sst.config.ts
export default {
config() {
return { name: "my-app", region: "us-east-1" };
},
stacks(app) {
app.stack(function API({ stack }) {
const api = new Api(stack, "api", {
routes: {
"GET /users": "packages/functions/src/users.list",
"POST /users": "packages/functions/src/users.create",
},
});
stack.addOutputs({ ApiEndpoint: api.url });
});
},
};Serverless Framework
Framework được dùng rộng rãi nhất. Support AWS, Azure, GCP.
# serverless.yml
service: my-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
functions:
getUsers:
handler: src/handlers/users.getAll
events:
- http:
path: /users
method: get
processOrder:
handler: src/handlers/orders.process
events:
- sqs:
arn: !GetAtt OrderQueue.Arn
batchSize: 10
resources:
Resources:
OrderQueue:
Type: AWS::SQS::QueueAWS SAM (Serverless Application Model)
Framework native AWS dùng CloudFormation template.
# template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
GetUsersFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/users.getAll
Runtime: nodejs20.x
Events:
GetUsers:
Type: Api
Properties:
Path: /users
Method: getSo Sánh Framework
| Feature | SST | Serverless Framework | AWS SAM |
|---|---|---|---|
| Cloud support | AWS | AWS, Azure, GCP | Chỉ AWS |
| Ngôn ngữ | TypeScript | YAML + plugin | YAML (CloudFormation) |
| Local dev | Live Lambda (hot reload) | Offline plugin | Local invoke |
| Full-stack | Next.js, Remix, Astro | API-focused | API-focused |
| Learning curve | Trung bình | Thấp | Trung bình |
Khi Nào Serverless Phù Hợp
Rất Phù Hợp
1. API với traffic biến động
Sáng thứ Hai: 10,000 request/phút
Tối thứ Bảy: 50 request/phút
→ Serverless auto-scale và gần như không tốn tiền khi idle2. Event processing pipeline
3. Scheduled task và cron job
Daily report generation → 1 Lambda invocation mỗi ngày
Weekly email digest → 1 invocation mỗi tuần
→ Trả tiền cho vài giây compute, không phải server 24/74. Webhook và integration
Function chờ callback từ bên thứ ba (Stripe, GitHub, Slack).
5. Prototype và MVP
Ship nhanh với zero infrastructure management. Scale sau nếu cần.
Không Phù Hợp
1. Long-running process
Video transcoding, ML model training, batch ETL job chạy hàng giờ.
2. WebSocket / persistent connection
FaaS tiêu chuẩn không support long-lived connection (dù Cloudflare Workers Durable Objects và AWS API Gateway WebSocket thì có).
3. Consistent high-throughput
Nếu bạn process 50,000 request/giây ổn định, container hoặc VM rẻ hơn đáng kể.
4. Local state requirement
In-memory cache, session state, hoặc data phải persist giữa các request.
Hybrid Architecture
Đa số production system dùng kết hợp serverless và component truyền thống.
Pattern phổ biến:
- API layer: Serverless (auto-scaling, pay-per-use)
- Real-time: Always-on WebSocket server
- Background processing: Queue + serverless worker (cho task ngắn) hoặc container (cho task dài)
- Database: Serverless-native (Neon, DynamoDB)
- Caching: Serverless Redis (Upstash)
Tổng Kết
Serverless architecture chuyển trách nhiệm operation từ bạn sang cloud provider, cho phép bạn tập trung vào code thay vì infrastructure.
Concept cốt lõi:
- FaaS — deploy function riêng lẻ tự động scale
- BaaS — dùng managed service (auth, database, storage) thay vì tự build
- Stateless design — function không có shared memory; dùng external state store
- Event-driven — function phản hồi trigger (HTTP, queue, schedule, storage event)
Lợi ích chính:
- Zero server management (không provisioning, patching, scaling decision)
- Pay-per-use pricing (không tốn tiền khi idle)
- Automatic scaling từ zero đến hàng ngàn concurrent invocation
- Time-to-market nhanh hơn (focus vào business logic, không phải infrastructure)
Thách thức chính:
- Cold start thêm latency (giảm bằng provisioned concurrency, runtime nhanh)
- Execution time limit (tối đa 15 phút trên Lambda)
- Vendor lock-in (giữ business logic portable)
- Debugging và observability khó hơn (distributed tracing, structured logging)
- Connection management với traditional database (dùng serverless database)
Khi nào nên dùng:
- Traffic biến động hoặc không dự đoán được
- Event processing, webhook, scheduled task
- API nơi per-endpoint scaling hợp lý
- Prototype, MVP, và product giai đoạn đầu
Khi nào nên tránh:
- Workload high-throughput ổn định (rẻ hơn với server)
- Long-running process (>15 phút)
- WebSocket hoặc persistent connection requirement
- Heavy compute (ML training, video processing)
Serverless không phải về loại bỏ server — mà về loại bỏ các quyết định về server. Để platform xử lý capacity, scaling, và availability trong khi bạn build thứ quan trọng.
Tiếp Theo Trong Software Architecture Series
Đây là bài 11 trong 12 bài của series Software Architecture Patterns:
- ✅ ARCH-1: Software Architecture Patterns Roadmap
- ✅ ARCH-2: Monolithic Architecture
- ✅ ARCH-3: Layered (N-Tier) Architecture
- ✅ ARCH-4: MVC, MVP & MVVM Patterns
- ✅ ARCH-5: Microservices Architecture
- ✅ ARCH-6: Event-Driven Architecture
- ✅ ARCH-7: CQRS & Event Sourcing
- ✅ ARCH-8: Hexagonal Architecture
- ✅ ARCH-9: Clean Architecture
- ✅ ARCH-10: Domain-Driven Design (DDD)
- ✅ ARCH-11: Serverless & FaaS Architecture (bài này)
- 🔜 ARCH-12: Choosing the Right Architecture
Bài liên quan:
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.