// acl/legacyCustomer.ts
export function toCustomer(row: LegacyCustRow): Customer {
return {
id: row.CUST_ID.trim(),
tier: ({ "1": "standard", "3": "gold" } as const)[row.CUST_TP] ?? "standard",
email: row.EMAIL_ADDR?.toLowerCase() ?? null,
};
}
Monolith → Microservices
draftedMonolith vs modular monolith vs microservices, DDD bounded contexts, strangler fig, backward compatibility & contract testing, database-per-service, sync vs async, API gateway/BFF, service mesh, tracing, distributed monolith.
0/47 rated · mastery 0%
Bài học
11 bài · ~128 phút đọc. Học theo thứ tự, rồi làm câu hỏi.- 01~13 phút· 3 câuverify
Monolith, modular monolith và microservices
Ba kiểu kiến trúc giải quyết ba bài toán khác nhau: cái giá thật của một network boundary (availability nhân dồn, latency, mất ACID), cách giữ ranh giới cứng trong một modular monolith, khi nào không nên dùng microservices, và tiêu chí đặt một capability mới vào monolith hay service riêng.
- 02~13 phút· 5 câuverify
Bounded context, Conway's law và tìm ranh giới service
Bounded context và ubiquitous language trong DDD, context map, cách tìm ranh giới trong một monolith legacy (event storming, quyền ghi dữ liệu, change coupling từ git log), Conway's law và inverse Conway, tổ chức 5 team cho 25 service, và cách kể câu chuyện decomposition của chính bạn.
- 03~11 phút· 3 câuverify
Database-per-service và query dữ liệu xuyên service
Database-per-service nghĩa là gì (và không bắt buộc là gì), vì sao shared database là API ngầm phá deploy độc lập, cách enforce bằng schema + role trong Postgres, query xuyên service bằng API composition hay CQRS read model, xử lý read model chậm vài giây, báo cáo từ năm service, và tách một bảng `customers` dùng chung không downtime.
- 04~11 phút· 5 câuverify
Strangler fig và anti-corruption layer
Migrate một monolith đang chạy mà không big-bang: facade route dần từng capability, chọn service đầu tiên, chuyển traffic theo tenant với kill switch, shadow traffic để so sánh kết quả, rollback cả routing lẫn dữ liệu, và anti-corruption layer gom mọi phép dịch dữ liệu legacy (mã trạng thái, CHAR có padding, giờ địa phương) vào một chỗ có test.
- 05~11 phút· 4 câuverify
Đồng bộ dữ liệu khi migrate: CDC, outbox và một owner duy nhất
Giữ dữ liệu của service mới và legacy đồng bộ trong suốt strangler migration: CDC đọc WAL (chạy thật với logical decoding của Postgres), transactional outbox với relay SKIP LOCKED, vì sao dual write không atomic, source of truth theo từng giai đoạn, sự cố hai writer cùng ghi status, chuyển ownership theo tenant, reconciliation, và cách kể sự cố production khó nhất.
- 06~11 phút· 5 câuverify
Backward compatibility, versioning và expand/contract
Thay đổi nào của HTTP API và event là an toàn, thay đổi nào breaking (kể cả thêm enum), tolerant reader và vì sao `.strict()` làm vỡ consumer (chạy thật với zod 4), các chiến lược version (URL, header, evolution), deprecation có số liệu, expand/contract cho schema và API dưới rolling deploy (chạy thật trên Postgres), và quy trình sau sự cố đổi tên field.
- 07~11 phút· 3 câuverify
Contract testing và chiến lược test cho microservices
Consumer-driven contract với Pact (chạy thật: consumer test sinh pact, provider verify pass rồi fail khi đổi tên field), can-i-deploy, contract cho event, phát hiện breaking change trong OpenAPI bằng oasdiff (chạy thật, kể cả thêm enum), chiến lược test cho 20 service và luồng async qua Kafka, và cách đặt chuẩn API/event cho nhiều team mà không thành nút cổ chai.
- 08~12 phút· 4 câuverify
Giao tiếp sync vs async và resilience trên mỗi call
Khi nào gọi đồng bộ, khi nào phát event; temporal coupling và availability nhân dồn; sửa một handler Express không timeout, không kiểm tra `r.ok`, gọi tuần tự (chạy thật: 200/200 request treo khi dependency kẹt), circuit breaker với opossum, N+1 qua network trong BFF (2282 ms → 25 ms), và cách điều tra khi p99 tăng gấp đôi sau khi tách service.
- 09~12 phút· 5 câuverify
API gateway, BFF, service discovery và service mesh
Gateway làm gì và không nên làm gì, BFF cho từng loại client và cách giữ domain logic ra khỏi BFF, service discovery với Kubernetes Service/DNS và vì sao gRPC long-lived connection phá load balancing, service mesh (sidecar, ambient) khi nào đáng, retry ở app cộng mesh nhân tải (đo thật: 27 request cho 1), và authentication ở edge + authorization trong từng service (chạy thật: BOLA, spoofed header, audience sai).
- 10~12 phút· 4 câuverify
Distributed tracing, health probes và twelve-factor
Trace, span và W3C `traceparent`; OpenTelemetry trong Node (chạy thật: `fetch` không được propagate nếu thiếu instrumentation undici; inject/extract qua header Kafka), head vs tail sampling; liveness/readiness/startup probe và vì sao liveness không được check DB; twelve-factor cho service Node; graceful shutdown dưới Kubernetes (chạy thật: keep-alive giữ server sống thêm 5 giây); và debug lỗi chỉ xảy ra trên một số pod sau deploy.
- 11~12 phút· 6 câuverify
Distributed monolith, platform và đo hiệu quả migration
Distributed monolith: triệu chứng đo được và cách thoát; shared library nên chứa gì và không nên chứa gì; khi nào gộp service lại và làm thế nào; những năng lực platform phải có trước khi chạy nhiều service; đo migration bằng DORA và số deploy phải phối hợp (chạy thật trên một deploy log); và cách trả lời câu hỏi hồi cứu "bạn sẽ làm khác điều gì".
Tóm tắt & cheat sheet của track
TL;DR
- Microservices là cách tổ chức hệ thống quanh khả năng deploy và sở hữu độc lập của từng team — không phải mục tiêu tự thân. Cái giá: mọi function call thành network call, transaction thành saga, debug cần tracing.
- Thứ tự hợp lý thường là monolith → modular monolith → tách service khi có lý do rõ (scale khác nhau, team độc lập, release cadence, isolation lỗi/compliance).
- Ranh giới service đến từ bounded context (DDD) và dữ liệu mà service sở hữu (database-per-service). Ranh giới sai → distributed monolith: tất cả nhược điểm của cả hai.
- Migration an toàn = strangler fig (facade route dần), anti-corruption layer, đồng bộ dữ liệu (CDC/outbox), backward compatibility (expand/contract, tolerant reader, contract test), đo parity trước khi cắt.
- Hạ tầng tối thiểu trước khi tách: CI/CD mỗi service, observability (log có correlation id, metrics, distributed tracing), config/secrets, service discovery, gateway.
Khái niệm cốt lõi
| Khái niệm | Một câu | Ví dụ ngắn |
|---|---|---|
| Modular monolith | Một deploy unit, module có ranh giới cứng (API nội bộ, không truy cập DB của nhau) | orders/, catalog/ chỉ gọi nhau qua interface |
| Bounded context | Ranh giới nơi một model/ngôn ngữ có nghĩa nhất quán | "Product" ở Catalog ≠ "Product" ở Warehouse |
| Database per service | Mỗi service sở hữu dữ liệu; service khác chỉ qua API/event | Order service không JOIN bảng của Customer |
| Strangler fig | Facade phía trước legacy, chuyển dần từng route sang service mới | /api/invoices/* → Billing service |
| Anti-corruption layer | Lớp dịch giữa model legacy và model mới | Map CUST_TP=3 → CustomerTier.Gold |
| API gateway | Điểm vào chung: routing, auth, rate limit, TLS | Kong, AWS API Gateway, NGINX |
| BFF | Backend riêng cho từng loại client | mobile-bff gom 5 call thành 1 |
| Service discovery | Tìm địa chỉ instance động | K8s Service DNS, Consul |
| Service mesh | Sidecar/proxy lo mTLS, retry, traffic shifting, telemetry | Istio, Linkerd |
| Contract testing | Kiểm tra provider thoả mãn kỳ vọng của consumer | Pact |
| Distributed tracing | Theo dõi một request qua nhiều service bằng trace id | W3C traceparent, OpenTelemetry |
| Expand / contract | Thay đổi schema/API theo 2 pha tương thích | Thêm cột mới → migrate → bỏ cột cũ |
Cơ chế hoạt động
Strangler fig migration
Các bước cắt một capability
Trade-offs
| Monolith | Modular monolith | Microservices | |
|---|---|---|---|
| Deploy | Một đơn vị | Một đơn vị | Độc lập từng service |
| Transaction | ACID dễ | ACID dễ | Saga, eventual consistency |
| Scale team | Kém khi > vài team | Tốt vừa | Tốt nếu ranh giới đúng |
| Vận hành | Đơn giản | Đơn giản | Nặng: CI/CD, observability, network, on-call |
| Isolation lỗi | Thấp | Thấp | Cao (nếu có timeout/bulkhead) |
| Latency | Function call | Function call | Network hop mỗi call |
| Refactor ranh giới | Dễ | Dễ vừa | Đắt (API/data migration) |
| Giao tiếp | Ưu | Nhược | Dùng khi |
|---|---|---|---|
| Sync (REST/gRPC) | Đơn giản, có kết quả ngay | Temporal coupling, cascading failure, latency cộng dồn | Query cần trả lời ngay, validate |
| Async (event/queue) | Loose coupling, chịu lỗi, fan-out | Eventual consistency, debug khó, cần idempotency | Thông báo thay đổi, workflow dài, đồng bộ read model |
Pitfalls
- Tách theo layer kỹ thuật (auth-service, db-service, validation-service) thay vì theo capability nghiệp vụ.
- Shared database giữa service → không deploy/đổi schema độc lập được.
- Một user action cần gọi đồng bộ 5 service nối tiếp → availability = tích (0.99^5 ≈ 95%), latency cộng dồn.
- Deploy phải theo thứ tự ("B trước rồi mới A") → dấu hiệu distributed monolith.
- Shared library chứa domain model dùng chung → mọi service phải nâng version cùng lúc.
- Breaking API/event change không có versioning, không contract test.
- Tách service trước khi có CI/CD, tracing, dashboard → không biết lỗi ở đâu.
- Big-bang rewrite: đóng băng feature nhiều tháng rồi cắt một lần.
Cheat sheet
- Bắt đầu: modular monolith; tách khi có lý do đo được (team, scale, release, compliance).
- Ranh giới: bounded context + dữ liệu sở hữu + tần suất thay đổi cùng nhau (change coupling từ git log).
- Migration: strangler fig + ACL + CDC/outbox + shadow traffic + chuyển dần + rollback bằng routing.
- Tương thích: thêm field optional, không đổi nghĩa/xoá field khi còn consumer; tolerant reader; expand/contract; version khi buộc phải breaking; consumer-driven contract test trong CI.
- Data: database-per-service; query xuyên service bằng API composition hoặc read model (CQRS) cập nhật qua event.
- Mỗi call: timeout, retry có budget, circuit breaker, correlation/trace id (
traceparent). - Gateway: cross-cutting (auth, rate limit, routing), không chứa business logic; BFF cho nhu cầu riêng từng client.
- Distributed monolith smell: lockstep deploy, shared DB, chatty sync call, một thay đổi chạm nhiều service.
References
- Martin Fowler & James Lewis — Microservices ↗
- Martin Fowler — MonolithFirst ↗
- Martin Fowler — Strangler Fig Application ↗
- Martin Fowler — Bounded Context ↗
- Martin Fowler — Consumer-Driven Contracts ↗
- Martin Fowler — Tolerant Reader ↗
- microservices.io — Database per service ↗
- microservices.io — API gateway / Backends for Frontends ↗
- microservices.io — Strangler application ↗
- Sam Newman — Backends For Frontends ↗
- W3C Trace Context ↗
- OpenTelemetry documentation ↗
- Pact documentation ↗
- AWS Prescriptive Guidance — Strangler fig pattern ↗
Personal notes (Notion)
Ghi chú cá nhân — có thể sai hoặc lỗi thời, dùng để tham khảo.
Interview Questions
Showing 47 / 47
rename customers.fullname -> display_name
v1: add display_name (nullable); write both; backfill
v2: read display_name; still write both
v3: stop writing fullname
v4: drop fullname
app.get("/orders/:id", async (req, res) => {
const order = await db.order.findUnique({ where: { id: req.params.id } });
const customer = await fetch(`${CUSTOMER_URL}/customers/${order!.customerId}`)
.then((r) => r.json());
const pricing = await fetch(`${PRICING_URL}/quote`, {
method: "POST",
body: JSON.stringify(order),
}).then((r) => r.json());
res.json({ ...order, customer, pricing });
});
app.get("/bff/orders", async (req, res) => {
const orders = await ordersApi.list({ userId: req.user.id, limit: 50 });
const result = [];
for (const o of orders) {
const product = await catalogApi.get(o.productId);
const shipment = await shippingApi.get(o.id);
result.push({ ...o, product, shipment });
}
res.json(result);
});
// before: { "id": "c1", "email": "a@x.io", "tier": "gold" }
// after: { "id": "c1", "contact": { "email": "a@x.io" }, "tier": "GOLD", "segment": "b2b" }
const Customer = z.object({
id: z.string(),
email: z.string().email(),
tier: z.enum(["standard", "gold"]),
}).strict();
consumer.on("CustomerUpdated", (msg) => repo.upsert(Customer.parse(msg)));