1. Monolith vs Microservice
Monolith
1 codebase, 1 deployment, 1 process, 1 DB.
- (+) Đơn giản — đọc code, debug, deploy.
- (+) Transaction ACID nội bộ.
- (+) Refactor dễ — IDE rename hoạt động cross-module.
- (+) Dev local: chạy 1 command.
- (−) Codebase lớn → build/test chậm.
- (−) Scale toàn app, không scale phần cụ thể.
- (−) 1 bug crash toàn app.
- (−) Khó adopt tech mới (cùng stack).
Microservice
Nhiều service nhỏ, deploy độc lập, communicate qua network.
- (+) Scale theo service (auth heavy → 10 instance).
- (+) Deploy độc lập (đổi recommend không động checkout).
- (+) Tech polyglot (Go cho high perf, Python cho ML).
- (+) Team autonomy.
- (+) Fault isolation (1 service down không kéo cả app).
- (−) Distributed system complexity (CAP, eventual).
- (−) Operational overhead (monitoring, deploy, on-call).
- (−) Network latency (RPC chậm hơn function call 1000×).
- (−) Cross-service transaction khó (saga, không ACID).
- (−) Cross-cutting concern (auth, logging) phức tạp hơn.
1.1. Khi nào monolith?
- Team < 20 người.
- Domain chưa rõ — sẽ refactor nhiều.
- Prototype, MVP, startup early.
- App CRUD đơn giản, không cần scale phức tạp.
1.2. Khi nào microservice?
- Team > 50 người, nhiều team độc lập.
- Domain stable, BC rõ.
- Cần scale phần cụ thể (vd: video transcode tốn CPU, profile lookup nhẹ).
- Deploy frequency cao (nhiều service deploy/day).
- Có ngân sách operational cho phức tạp.
1.3. Modular Monolith — best of both?
1 deployment, nhưng code chia thành module rõ với boundaries nội bộ chặt. Không cross-module direct call — qua interface/event nội bộ. Khi cần split sau này, đã có boundaries sẵn.
monolith/
├── modules/
│ ├── billing/
│ │ ├── api/ (only this layer is "public")
│ │ ├── domain/
│ │ ├── infrastructure/
│ ├── shipping/
│ ├── inventory/
│ └── notification/
├── shared/ (only DTOs, contracts)
└── app.ts (composition root)
Mỗi module độc lập như microservice nhưng không có network overhead. Shopify, Basecamp dùng pattern này thành công với 100k+ LOC.
2. Distributed Monolith — Anti-pattern
Worst of both worlds: chia microservice nhưng coupling chặt. Đổi 1 service = phải deploy đồng thời nhiều service.
2.1. Dấu hiệu
- Service A gọi service B, B gọi C, C gọi D — chuỗi sync RPC.
- Service share database (cùng table).
- Deploy theo "release train" — tất cả service deploy cùng lúc.
- Local dev: chạy 1 service phải bật 10 service khác.
- Mỗi feature đụng vào 5+ service.
- Bug bụi này thấy ở service kia (network → khó hơn function call).
2.2. Vì sao tệ hơn monolith?
- Phức tạp microservice (network, distributed) + coupling monolith (deploy chung).
- Latency cao (5 hop × 5ms = 25ms baseline).
- 1 service down = chuỗi down.
- Hard to understand — dependency graph ẩn.
2.3. Phòng tránh
- Mỗi service có DB riêng (database-per-service).
- Communicate ưu tiên async (event) thay sync (RPC).
- Service boundary theo Bounded Context, không theo "tier" (web, business, data).
- Có thể deploy 1 service mà không động service khác → contract stable.
- Backward-compatible API — versioning.
3. Service Decomposition — chia theo nguyên lý nào?
3.1. Decompose by Business Capability
Mỗi service phục vụ 1 business capability rõ.
- User Management.
- Order Processing.
- Inventory Management.
- Shipping.
- Billing & Payment.
- Notification.
3.2. Decompose by Subdomain (DDD)
Đã học OOP Ch8 — Bounded Context. Mỗi BC có model riêng → service riêng tự nhiên.
3 loại subdomain:
- Core domain — competitive advantage (Uber: matching/routing). Đầu tư nhiều.
- Supporting domain — quan trọng nhưng không khác biệt (billing). Có thể outsource.
- Generic domain — commodity (auth). Dùng SaaS (Auth0, Okta).
3.3. Decompose by Strangler Fig Pattern
Migrate monolith → microservice gradually:
- Đặt API Gateway trước monolith.
- Identify 1 capability (vd: notification) → tạo service mới.
- Gateway route notification request đến service mới, còn lại đến monolith.
- Khi xong, monolith không còn code notification.
- Lặp lại cho capability khác.
- Cuối cùng, monolith "biến mất", chỉ còn microservice.
Tên "strangler fig" từ cây ficus thắt — lớn dần bóp chết cây chủ. Pattern Martin Fowler.
3.4. Anti-pattern decomposition
- Tier-based split — "frontend service", "business service", "data service". Đây là 3-tier monolith được rải qua network. KHÔNG.
- Entity-based split — "User Service", "Order Service" — đôi khi đúng nhưng đôi khi tạo coupling chặt vì 2 entity gắn chặt nghiệp vụ.
- Too small (nano-service) — 1 service cho 1 endpoint. Network overhead lớn hơn benefit.
3.5. Service size sweet spot
Quy tắc Amazon "two-pizza team" — 1 service do 1 team 6-8 người maintain. Không phải dòng code, mà là cognitive load.
Ngon nhất: service "đủ nhỏ để hiểu, đủ lớn để làm việc có ý nghĩa". Thường 5-50k LOC.
4. Inter-service Communication
4.1. Sync — REST
GET /api/v1/users/123
Host: user-service.local:8080
Accept: application/json
# Response
HTTP/1.1 200 OK
Content-Type: application/json
{ "id": 123, "name": "Alice" }
(+) Universal, debugging tools nhiều, dễ học.
(−) JSON parsing overhead, không type-safe.
4.2. Sync — gRPC
HTTP/2 + Protobuf. Nhanh hơn REST 5-10×, type-safe, streaming support.
// user.proto
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc StreamUsers(StreamRequest) returns (stream User);
}
message GetUserRequest { int64 id = 1; }
message User { int64 id = 1; string name = 2; }
(+) Performance, type-safe, code gen client/server.
(−) Browser support limited (cần grpc-web), debugging khó hơn (binary), learning curve.
4.3. Sync — GraphQL
1 endpoint, client request schema cụ thể. Tốt cho frontend BFF (Backend For Frontend).
Trong inter-service: hiếm dùng (REST/gRPC phổ biến hơn). GraphQL Federation có thể.
4.4. Async — Event/Message
Đã học Ch5. Service publish event, các service khác subscribe. Loose coupling.
4.5. Sync vs Async — chọn cái nào?
| Tình huống | Phù hợp |
|---|---|
| User chờ response (login, create order) | Sync (REST/gRPC) |
| Background work (email, transcode) | Async (queue) |
| Cross-service notification (order created → email) | Async (event) |
| Real-time data (chat, live update) | WebSocket / Server-Sent Events |
| Service-to-service query (cần data thật từ service khác) | Sync |
| Audit, analytics, log | Async (fire and forget) |
4.6. Pattern: Async-first với Sync fallback
Mặc định async; chỉ sync khi user thực sự cần response immediate. Giảm coupling, tăng resilience.
5. Service Mesh
Khi có 50+ service, vấn đề "cross-cutting" lặp ở mọi service:
- mTLS giữa service.
- Retry/timeout/circuit breaker.
- Load balancing.
- Tracing & metrics.
- Authn/Authz.
Service Mesh tách những concern này khỏi app code, đặt vào sidecar proxy đứng cạnh mỗi service.
5.1. Tools
- Istio — Google, phổ biến nhất, feature đầy đủ, complex.
- Linkerd — Buoyant, simpler, light, fast.
- Consul Connect — HashiCorp.
- AWS App Mesh — managed.
5.2. Trade-off
- (+) App code không cần biết về retry/mTLS/tracing.
- (+) Policy uniform: thay 1 file YAML, áp dụng toàn cluster.
- (+) Observability tự động: mọi traffic đi qua sidecar.
- (−) Latency overhead 1-3ms per hop (sidecar proxy).
- (−) Operational complexity (control plane, sidecar lifecycle).
- (−) Hardware overhead — sidecar tốn CPU/RAM.
Khi nào? > 30 microservice. Dưới đó, library-based (Resilience4j, Polly) đủ.
6. Resilience Patterns
6.1. Timeout
Đặt timeout cho mọi external call. Default không bao giờ infinite.
// ❌ No timeout — request có thể hang vô hạn
const result = await fetch(url);
// ✓ Timeout 5s
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 5000);
const result = await fetch(url, { signal: controller.signal });
clearTimeout(id);
Quy tắc: timeout chuỗi service phải giảm dần. Edge 30s, gateway 25s, service 20s, DB 15s. Tránh "outer chờ inner đã chết".
6.2. Retry với Exponential Backoff
async function withRetry<T>(fn: () => Promise<T>, max = 3): Promise<T> {
for (let i = 0; i < max; i++) {
try {
return await fn();
} catch (err) {
if (i === max - 1) throw err;
if (!isRetryable(err)) throw err; // không retry 400, chỉ 5xx + network error
const delay = Math.min(1000 * Math.pow(2, i) + Math.random() * 500, 30_000);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('unreachable');
}
Quy tắc:
- Chỉ retry idempotent operation (GET, PUT, DELETE) — không POST trừ khi có idempotency key.
- Backoff exponential + jitter — tránh retry storm.
- Cap max retry (3-5) — không vô hạn.
- Chỉ retry retryable error — 5xx, network error. KHÔNG retry 4xx.
6.3. Circuit Breaker
Kill request đến service đang fail. 3 state:
- Closed — gọi bình thường, đếm fail.
- Open — fail rate cao → mở mạch, mọi call fail ngay (không gọi service đang chết).
- Half-Open — sau timeout, thử 1 call để xem service đã recover.
Lợi: tránh overload service đang chết, fail-fast cho user.
Tools: Resilience4j (Java), Polly (.NET), Hystrix (deprecated), opossum (Node.js), Istio circuit-breaking config.
6.4. Bulkhead
Tách isolation — 1 service down không kéo phần khác.
Ví dụ: separate thread pool / connection pool cho mỗi downstream:
// ❌ Shared pool — service A chậm → user request không xử lý được
const sharedPool = new Pool({ max: 100 });
// ✓ Bulkhead — pool riêng cho mỗi downstream
const userServicePool = new Pool({ max: 50 });
const orderServicePool = new Pool({ max: 30 });
const billingServicePool = new Pool({ max: 20 });
Tên "bulkhead" từ tàu thủy — vách ngăn để 1 khoang ngập không kéo chìm tàu.
6.5. Fail-fast vs Fail-safe
- Fail-fast: detect lỗi sớm, throw ngay (timeout, circuit breaker).
- Fail-safe: degrade gracefully — trả default value, fallback cache, partial response.
Vd: recommendation service down → trả top sản phẩm hot thay vì error 500. UX tốt hơn.
7. Distributed Tracing
1 request đi qua 5 service. Service C trả 500. Làm sao biết B → C call đã có vấn đề từ A → B?
7.1. Trace, Span, Context
- Trace — toàn bộ "đường đi" của 1 request.
- Span — 1 đoạn (vd: A xử lý request, B xử lý request, DB query).
- Trace context — IDs propagate qua tất cả service:
trace-id,span-id,parent-span-id.
7.2. OpenTelemetry — chuẩn
Standard library cho tracing/metrics/logs. Vendor-neutral. Backend export (Jaeger, Tempo, Datadog, Honeycomb).
import { trace, context } from '@opentelemetry/api';
const tracer = trace.getTracer('order-service');
async function getOrder(id: string) {
return tracer.startActiveSpan('getOrder', async (span) => {
span.setAttribute('order.id', id);
try {
const order = await db.query('SELECT...', [id]);
span.setStatus({ code: 1 }); // OK
return order;
} catch (err) {
span.recordException(err);
span.setStatus({ code: 2 }); // ERROR
throw err;
} finally {
span.end();
}
});
}
7.3. Sampling
Trace mọi request quá tốn — sample. Strategies:
- Head sampling — quyết định ở entry, vd: 1% mọi request.
- Tail sampling — đợi xong rồi quyết định lưu. Lưu mọi request slow + error, sample 1% successful.
- Probabilistic — random N%.
- Rate-limited — max 10 trace/s mỗi service.
7.4. 3 pillars of observability
- Logs — text events. Khi query "what happened at 14:23".
- Metrics — numeric time-series. "error rate, latency, QPS" — Prometheus.
- Traces — request flow cross-service. Jaeger, Tempo.
Logs đắt nhất, metrics rẻ nhất. Trace cân bằng. Tốt nhất: cả 3 với common correlation ID.
8. Deployment Patterns
8.1. Blue-Green Deployment
2 environment giống nhau:
- Blue = production hiện tại.
- Green = version mới deployed.
Test green xong → switch traffic blue → green qua LB. Sự cố → switch ngược ngay.
(+) Rollback dưới giây. (−) Tốn 2× hardware lúc deploy.
8.2. Canary Deployment
Route N% (5%) traffic đến version mới. Monitor metrics. OK → tăng dần (10%, 25%, 50%, 100%). Lỗi → rollback.
Tốt hơn blue-green vì phát hiện bug với traffic thực dần dần.
8.3. Rolling Deployment
Replace instance từng ít một. 10 instance → kill 2, deploy 2 new, repeat. Kubernetes default.
(+) Không tốn thêm hardware. (−) Có thời gian cùng tồn tại 2 version → API phải backward-compatible.
8.4. Feature Flags
Deploy code có feature mới nhưng wrap trong flag. Flag off → behavior cũ. Flag on → behavior mới.
if (await featureFlag.isEnabled('new-checkout-flow', { userId })) {
return newCheckout(req);
}
return oldCheckout(req);
Lợi:
- Decouple deploy với release — code đã prod nhưng off.
- A/B test — bật cho 10% user.
- Kill switch — feature buggy → tắt ngay không cần redeploy.
- Gradual rollout — bật theo region, theo user segment.
Tools: LaunchDarkly, Split.io, Unleash, GrowthBook (open-source). Hoặc DIY với DB/Redis flag.
8.5. Shadow Deployment
Deploy version mới, mirror traffic — không trả response cho user. Compare output với version cũ. Phát hiện bug trước khi thực sự switch.
8.6. CI/CD pipeline cơ bản
- Code → git push.
- CI: run test (unit + integration).
- Build image (Docker).
- Deploy staging → smoke test.
- Deploy canary 5% prod.
- Auto-monitor 30 phút.
- Auto-promote 100% nếu metric OK; rollback nếu fail.
9. Bài tập
- Đánh giá: project bạn đang làm phù hợp Monolith, Modular Monolith, hay Microservice? Lý giải 3 yếu tố cụ thể.
- Cho domain e-commerce, đề xuất 5-7 microservice theo Bounded Context. Vẽ dependency graph + xác định async vs sync giữa các service.
- Implement Circuit Breaker đơn giản (in-memory) cho HTTP client: 50% fail trong 30s → open, sau 30s thử lại. Test với mock service đôi khi return 500.
- Setup distributed tracing local: 3 service Node.js + Jaeger. Trace 1 request qua chain. Quan sát Jaeger UI.
- Phát hiện distributed monolith: cho 4 service, mỗi service dùng chung 1 PostgresDB và gọi nhau qua REST chain. Đề xuất refactor.
- Thiết kế feature flag system đơn giản: bật flag "new-search" cho 10% user dựa hash userId. Có thể tăng dần lên 100% qua dashboard.
- Canary deployment với nginx: route 5% traffic v2, 95% v1. Auto-rollback khi error rate v2 > 2%.
- Khi nào dùng gRPC thay REST cho service-to-service? Cho 2 ví dụ cụ thể.
10. Quiz
Quiz cuối Chương 6
Distributed Monolith là:
Martin Fowler khuyên về microservice:
Circuit Breaker pattern có 3 state:
Bulkhead pattern:
Service Mesh (Istio, Linkerd) tách:
"Strangler Fig Pattern" cho migrate monolith:
Distributed tracing core concept:
Feature flags decouple:
Hoàn thành Chương 6. Tiếp theo: Chương 7 — CDN, Rate Limiting, API Gateway, Security →