Giao diện
Monolith vs Microservices — Chọn đúng kiến trúc
Năm 2015, một startup fintech tại Singapore quyết định xây dựng hệ thống thanh toán bằng kiến trúc microservices từ ngày đầu tiên. Họ có 4 kỹ sư. Sau 8 tháng, thay vì ship sản phẩm, họ vẫn đang debug distributed tracing giữa 12 services — mỗi service do cùng một người viết cả frontend lẫn backend. Cuối cùng họ viết lại thành monolith, ship trong 6 tuần, và scale đến 50.000 transactions/ngày trước khi cần tách service đầu tiên.
Câu chuyện này lặp đi lặp lại ở hàng trăm công ty. Microservices không phải "bước tiến hóa tự nhiên" của monolith — nó là một chiến lược tổ chức có chi phí cực cao. Netflix, Amazon, Uber chọn microservices không phải vì nó "tốt hơn", mà vì quy mô tổ chức của họ bắt buộc phải tách.
Bài học quan trọng nhất trong kiến trúc phần mềm: không có kiến trúc nào inherently tốt hơn kiến trúc nào. Chỉ có kiến trúc phù hợp hoặc không phù hợp với bối cảnh cụ thể.
Bức tranh tư duy
Hãy tưởng tượng bạn mở một quán phở. Ban đầu, bạn — chủ quán — vừa nấu, vừa phục vụ, vừa thu tiền. Đây là monolith: mọi thứ trong một, trao đổi thông tin bằng cách quay đầu lại. Nhanh, đơn giản, hiệu quả cho quy mô nhỏ.
Khi quán đông lên 200 khách/ngày, bạn thuê thêm người: một đầu bếp, một phục vụ, một thu ngân. Mỗi người có trách nhiệm riêng nhưng vẫn trong cùng một không gian. Đây là modular monolith — tách trách nhiệm nhưng vẫn chung "mái nhà" (process, database).
Khi bạn mở chuỗi 20 chi nhánh trên toàn thành phố, mỗi chi nhánh hoạt động độc lập với bếp, nhân viên, hệ thống POS riêng. Đây là microservices. Nhưng bây giờ bạn cần hệ thống logistics, inventory trung tâm, báo cáo tài chính tổng hợp — đó chính là service mesh, API gateway, distributed tracing. Chi phí vận hành tăng gấp 10 lần.
Giới hạn của phép so sánh: Quán phở không có "deployment" hay "rollback". Trong thực tế, monolith có thể phục vụ hàng triệu request/giây (Stack Overflow chạy monolith .NET phục vụ 1.3 tỷ page views/tháng). Phép so sánh này chỉ minh họa tại sao tách nhỏ tạo overhead — không phải monolith "chỉ dành cho quy mô nhỏ".
Cốt lõi kỹ thuật
Monolith — Sức mạnh của sự đơn giản
Monolith triển khai toàn bộ ứng dụng trong một deployment unit duy nhất. Tất cả modules chia sẻ chung process, memory space và database.
Ưu điểm production thực tế:
- Transaction ACID đơn giản: Một database, một transaction boundary. Không cần Saga pattern.
- Debugging trực tiếp: Stack trace từ đầu đến cuối, không cần distributed tracing.
- Latency thấp: Function call trong cùng process — nanoseconds thay vì milliseconds qua network.
- Deployment đơn giản: Một artifact, một pipeline, một rollback command.
Khi nào monolith là lựa chọn đúng: Team dưới 20 người, domain chưa rõ boundaries, cần iterate nhanh, startup giai đoạn tìm product-market fit.
Modular Monolith — Điểm cân bằng bị bỏ quên
Modular Monolith tách code thành các module có boundary rõ ràng, giao tiếp qua interface — nhưng vẫn deploy và chạy trong cùng process.
src/
├── modules/
│ ├── order/ # Order module
│ │ ├── api/ # Public interface
│ │ ├── domain/ # Business logic
│ │ └── infra/ # Database, external calls
│ ├── payment/ # Payment module
│ │ ├── api/
│ │ ├── domain/
│ │ └── infra/
│ └── inventory/ # Inventory module
│ ├── api/
│ ├── domain/
│ └── infra/
└── shared/ # Shared kernel (minimal)Quy tắc vàng: Module A không bao giờ truy cập trực tiếp database tables của Module B. Giao tiếp qua public API interface. Vi phạm quy tắc này → distributed monolith khi tách service.
Microservices — Giải pháp cho vấn đề tổ chức
Microservices tách ứng dụng thành các service độc lập, mỗi service có codebase, database, deployment pipeline riêng.
Chi phí thực tế mà ít người nói đến:
| Yếu tố | Monolith | Microservices |
|---|---|---|
| Deployment pipeline | 1 | N pipelines (N = số services) |
| Monitoring | Application metrics | Distributed tracing + Service mesh |
| Data consistency | ACID transactions | Saga/Eventual consistency |
| Debugging | Stack trace | Correlation IDs across services |
| Latency overhead | ~0 (in-process) | 1-10ms per network hop |
| Team cần thiết | 1 DevOps | Platform team 3-5 người |
| Chi phí infrastructure (ước lượng) | $500-2K/tháng | $5K-50K/tháng |
Strangler Fig Pattern — Con đường migration an toàn
Đặt tên theo cây đa bóp nghẹt (Strangler Fig) — loài cây bắt đầu bám vào cây chủ, dần dần thay thế hoàn toàn. Pattern này cho phép migrate từ monolith sang microservices từng phần một, không cần "big bang rewrite".
Ba giai đoạn thực thi:
- Intercept: Đặt proxy/gateway phía trước monolith, route traffic dựa trên path.
- Shadow & Compare: Service mới chạy song song, so sánh kết quả, KHÔNG ảnh hưởng production.
- Cutover: Chuyển traffic dần dần (canary deployment) từ monolith sang service mới.
Database per Service — Nguyên tắc nền tảng
Mỗi microservice sở hữu database riêng. Không có service nào được truy cập trực tiếp database của service khác — chỉ giao tiếp qua API hoặc events.
Tại sao bắt buộc:
- Independent deployment: Schema change ở Service A không break Service B
- Technology freedom: Order Service dùng PostgreSQL, Search Service dùng Elasticsearch
- Independent scaling: Scale database của hot service mà không ảnh hưởng các service khác
Thách thức thực tế:
- Cross-service queries: Không thể JOIN giữa 2 databases → phải dùng API composition hoặc CQRS
- Data consistency: Không có cross-database ACID → phải dùng Saga Pattern
- Data duplication: Mỗi service có thể cache/copy dữ liệu từ service khác → eventual consistency
Service Mesh — Infrastructure layer cho microservices
Service Mesh (Istio, Linkerd) cung cấp networking layer giữa các services: load balancing, mTLS, circuit breaking, observability — mà không cần thay đổi application code.
Khi nào cần Service Mesh: Trên 10 services, cần mTLS giữa tất cả services, cần traffic management phức tạp (canary, A/B testing ở network level). Dưới 10 services — Service Mesh là overkill.
Team Topology — Conway's Law trong thực tế
Conway's Law: "Tổ chức nào thiết kế hệ thống sẽ tạo ra thiết kế phản ánh cấu trúc giao tiếp của tổ chức đó."
Inverse Conway Maneuver: Thay vì để kiến trúc phản ánh tổ chức hiện tại, hãy tổ chức team theo kiến trúc mục tiêu.
| Cấu trúc team | Kiến trúc phù hợp |
|---|---|
| 1 team 5-8 người | Monolith hoặc Modular Monolith |
| 2-4 teams, mỗi team sở hữu 1 domain | Modular Monolith → tách service khi cần |
| 5+ teams, mỗi team sở hữu service riêng | Microservices với platform team hỗ trợ |
Thực chiến
Tình huống: Migration từ Monolith — Hệ thống e-commerce 2 triệu users
Bối cảnh: Hệ thống e-commerce PHP monolith, 2 triệu active users, 15.000 orders/ngày. Database MySQL 500GB, deploy cycle 2 tuần, team 25 kỹ sư chia 4 squads. Mỗi lần deploy, cả 4 squads phải coordinate → deployment queue dài 3-4 tuần.
Mục tiêu: Giảm deploy cycle xuống dưới 1 ngày cho mỗi squad, tách Payment domain để comply PCI-DSS.
Chiến lược Strangler Fig — 6 tháng thực thi:
Tháng 1-2: Đặt API Gateway (Kong) phía trước monolith
Route 100% traffic qua gateway
Zero downtime, zero code change
Tháng 3-4: Tách Payment Service (Go)
Database riêng (PostgreSQL)
Shadow traffic 2 tuần → canary 10% → 50% → 100%
Kết quả: deploy Payment độc lập, PCI scope thu hẹp 80%
Tháng 5-6: Tách Inventory Service (Go)
Event-driven sync với monolith qua Kafka
Monolith vẫn giữ Order, User, CatalogKết quả đo được:
- Deploy frequency: 1 lần/2 tuần → 3 lần/ngày (cho Payment team)
- Mean time to recovery (MTTR): 45 phút → 8 phút
- PCI-DSS audit scope: giảm 80%
- Infrastructure cost: tăng 40% (thêm Kafka, Kong, monitoring)
Phân tích:
- Chỉ tách 2 services trong 6 tháng — không phải 10+. Mỗi service tách ra phải justify được bằng business value cụ thể.
- Monolith vẫn chạy cho 60% features — và điều đó hoàn toàn acceptable.
- Infrastructure cost tăng là trade-off được chấp nhận vì deployment independence quan trọng hơn.
Tình huống: Khi nào KHÔNG nên migrate
Bối cảnh: SaaS B2B platform, 500 enterprise clients, Rails monolith, team 12 người. CTO muốn chuyển sang microservices vì "khó scale".
Phân tích thực tế: Traffic peak 3.000 RPM, database 50GB, 99.95% uptime. Vấn đề thực sự không phải architecture — mà là:
- Thiếu caching layer (thêm Redis → giảm 60% database load)
- N+1 queries (fix 15 queries → response time giảm 70%)
- Thiếu CDN cho static assets
Quyết định: Giữ monolith, đầu tư vào performance optimization và modular boundaries. Kết quả: xử lý 10x traffic hiện tại với cùng infrastructure, không tăng operational complexity.
Real-world Migration Stories — Bài học từ các tổ chức lớn
Shopify (2019-2022): Bắt đầu là Rails monolith lớn nhất thế giới. Thay vì tách microservices, họ chọn Modular Monolith — tổ chức code thành components với boundaries rõ ràng trong cùng một Rails app. Kết quả: giữ được deployment simplicity, tăng team autonomy bằng component ownership. Chỉ một số ít services thực sự tách ra (Checkout, Payments).
Segment (2018): Đã migrate từ monolith sang 140+ microservices, sau đó migrate ngược về modular monolith. Lý do: operational overhead quá cao cho team size. Blog post nổi tiếng "Goodbye Microservices" trở thành case study kinh điển về premature microservices adoption.
Amazon: Ban đầu là monolith. Chỉ bắt đầu tách services khi team growth khiến monolith trở thành deployment bottleneck (2002-2006). Migration kéo dài nhiều năm, từng service một. Jeff Bezos nổi tiếng với "API mandate" — tất cả teams phải giao tiếp qua APIs.
Bài học chung: Không có tổ chức nào thành công bằng "big bang" migration. Tất cả đều dùng incremental approach — Strangler Fig hoặc variants của nó.
Sai lầm điển hình
❌ Sai lầm 1: "Netflix dùng microservices, nên startup mình cũng nên"
Vấn đề: Cargo cult — copy kiến trúc của tổ chức 10.000 kỹ sư cho team 5 người.
Tại sao sai: Netflix có 700+ microservices và hàng trăm kỹ sư platform xây dựng internal tools (Zuul, Eureka, Hystrix). Startup bạn không có luxury đó. Chi phí vận hành microservices cho team nhỏ lớn hơn lợi ích mà nó mang lại.
ĐÚNG: Bắt đầu với monolith hoặc modular monolith. Tách service chỉ khi có nhu cầu cụ thể — independent deployment, different scaling requirements, hoặc team autonomy.
❌ Sai lầm 2: Shared database giữa microservices
Vấn đề: Hai services cùng đọc/ghi vào một database.
// SAI: Order Service và Inventory Service chia sẻ database
OrderService ──┐
├──> MySQL (shared tables)
InventoryService┘Tại sao sai: Coupling ở database level biến microservices thành distributed monolith. Schema change ở một service break service kia. Không thể scale database độc lập.
// ĐÚNG: Mỗi service sở hữu database riêng, giao tiếp qua API/Events
OrderService ──> PostgreSQL (orders)
│
└── Event: "OrderCreated"
│
InventoryService ──> PostgreSQL (inventory)❌ Sai lầm 3: Synchronous communication chain
Vấn đề: Service A gọi B, B gọi C, C gọi D — tất cả synchronous HTTP.
Tại sao sai: Latency cộng dồn (A phải đợi B + C + D). Nếu D down, cả chain down. Availability = 99.9% × 99.9% × 99.9% × 99.9% = 99.6%. Mỗi service thêm vào chain giảm overall availability.
ĐÚNG: Dùng async messaging cho flows không cần response ngay lập tức. Chỉ sync call khi caller thực sự cần dữ liệu ngay để tiếp tục xử lý.
❌ Sai lầm 4: Big bang rewrite
Vấn đề: Dừng feature development 6-12 tháng để rewrite toàn bộ monolith thành microservices.
Tại sao sai: Business không đợi được. Yêu cầu thay đổi trong 6 tháng → rewrite bắt đầu lại từ đầu. Hầu hết big bang rewrites thất bại hoặc deliver ít hơn hệ thống cũ.
ĐÚNG: Strangler Fig Pattern — migrate từng phần, mỗi phần deliver business value ngay lập tức. Monolith và services mới chạy song song.
Under the Hood
Hiệu năng so sánh
| Metric | Monolith | Microservices | Ghi chú |
|---|---|---|---|
| Inter-service latency | ~0 (in-process call) | 1-10ms (network) | P99 có thể lên 50-100ms |
| Throughput per instance | Cao (shared memory) | Thấp hơn (serialization overhead) | gRPC nhanh hơn REST ~5-10x |
| Cold start | 1 application | N applications | Microservices: tổng startup time lâu hơn |
| Memory footprint | 1 JVM/process | N JVMs/processes | Mỗi JVM ~256MB-1GB base |
| Database connections | 1 connection pool | N connection pools | Connection pool exhaustion là vấn đề thực tế |
Chi phí ẩn của Microservices
Infrastructure: Service mesh (Istio) tiêu tốn 100-200MB RAM per sidecar proxy. 50 services = 5-10GB RAM chỉ cho sidecar proxies.
Observability stack: Prometheus + Grafana + Jaeger + ELK stack — ước lượng $1.000-5.000/tháng cho SaaS solutions, hoặc 2-3 kỹ sư full-time nếu self-hosted.
Development velocity ban đầu: Microservices chậm hơn monolith 30-50% trong 6 tháng đầu (thiết lập infrastructure, CI/CD per service, shared libraries).
Scaling Limits
Monolith: Vertical scaling đến giới hạn hardware (~256GB RAM, 96 cores). Horizontal scaling bằng multiple instances phía sau load balancer — hoạt động tốt cho stateless workloads. Stack Overflow chứng minh monolith có thể phục vụ quy mô cực lớn với hardware tốt.
Microservices: Horizontal scaling per service — scale chỉ phần cần thiết. Nhưng: inter-service communication trở thành bottleneck ở quy mô lớn. Service mesh giúp nhưng thêm latency 0.5-2ms per hop.
Trade-offs tổng hợp
| Tiêu chí | Chọn Monolith khi... | Chọn Microservices khi... |
|---|---|---|
| Team size | < 20 kỹ sư | > 20 kỹ sư, nhiều squads |
| Domain clarity | Boundaries chưa rõ | Bounded contexts đã stable |
| Deployment needs | 1 team deploy không conflict | Nhiều teams cần deploy độc lập |
| Compliance | Không có yêu cầu isolation | PCI-DSS, HIPAA cần scope isolation |
| Time to market | Cần ship nhanh | Có thời gian đầu tư infrastructure |
Checklist ghi nhớ
✅ Checklist triển khai
Đánh giá kiến trúc
- [ ] Xác định team size và cấu trúc tổ chức trước khi chọn kiến trúc
- [ ] Liệt kê bounded contexts từ business domain
- [ ] Đánh giá deployment frequency mục tiêu cho mỗi domain
- [ ] Tính toán chi phí infrastructure cho mỗi phương án kiến trúc
Monolith health check
- [ ] Code tổ chức theo modules với boundaries rõ ràng
- [ ] Không có circular dependencies giữa modules
- [ ] Database access chỉ qua module's own repository layer
- [ ] Deploy pipeline chạy dưới 15 phút
Migration readiness
- [ ] Có API Gateway/proxy phía trước monolith
- [ ] Đã xác định service đầu tiên cần tách (highest business value)
- [ ] Có monitoring và distributed tracing sẵn sàng
- [ ] Team có kinh nghiệm vận hành container orchestration (K8s)
- [ ] Có chiến lược data sync giữa monolith và service mới
Bài tập luyện tập
Bài 1: Phân tích kiến trúc — Intermediate
Đề bài: Một ứng dụng food delivery có các module: User, Restaurant, Order, Payment, Delivery, Notification. Team hiện có 15 kỹ sư, chia 3 squads. Monolith đang gặp vấn đề deploy conflict (3 squads cùng merge vào main branch). Hãy đề xuất chiến lược kiến trúc.
🧠 Quiz
Câu hỏi kiểm tra: Với bối cảnh trên, đâu là chiến lược phù hợp nhất?
- [ ] A. Tách ngay 6 microservices cho 6 modules
- [x] B. Chuyển sang Modular Monolith, tách Payment service riêng vì PCI compliance
- [ ] C. Giữ monolith, thêm feature flags để giảm deploy conflict
- [ ] D. Tách 3 services tương ứng 3 squads Giải thích: 15 kỹ sư chưa đủ để vận hành 6 microservices (cần platform team riêng). Modular Monolith giải quyết deploy conflict bằng module boundaries + trunk-based development. Payment tách riêng vì có nhu cầu compliance rõ ràng. Option D tách theo team thay vì domain — vi phạm bounded context.
💡 Gợi ý
- Áp dụng Conway's Law ngược: team structure → architecture
- Tính chi phí vận hành mỗi service: CI/CD, monitoring, on-call
- Tìm service có nhu cầu compliance hoặc scaling khác biệt rõ nhất
✅ Lời giải chi tiết
Chiến lược đề xuất:
Phase 1 (Tháng 1-3): Modular Monolith
├── Module: User + Auth
├── Module: Restaurant + Catalog
├── Module: Order + Payment (tạm thời)
├── Module: Delivery + Notification
└── Shared: Database abstractions, Event bus (in-process)
Phase 2 (Tháng 4-6): Tách Payment Service
├── Payment Service (riêng database, PCI scope)
├── Event-driven sync: OrderCreated → PaymentRequested
└── Monolith giữ tất cả modules còn lại
Phase 3 (Tháng 7+): Đánh giá tiếp
├── Nếu team grow lên 25+: xem xét tách Delivery Service
├── Nếu traffic spike: scale Payment và Order độc lập
└── Nếu ổn định: giữ nguyên, tối ưu hiện tạiLý do: 15 kỹ sư / 3 squads = 5 người/squad. Mỗi squad sở hữu 1-2 modules trong monolith. Deploy conflict giải quyết bằng module boundaries + feature flags. Chỉ Payment tách vì PCI-DSS là non-negotiable business requirement.
Bài 2: Strangler Fig Design — Intermediate
Đề bài: Bạn được giao migrate user authentication từ Rails monolith sang Go service. Monolith hiện xử lý 5.000 login requests/phút. Hãy thiết kế plan chi tiết cho Strangler Fig migration, bao gồm: routing strategy, data migration, rollback plan.
💡 Gợi ý
- Bắt đầu từ read path (validate token) trước write path (login/register)
- Dual-write là anti-pattern — dùng event-based sync
- Luôn có "kill switch" để route 100% traffic về monolith
✅ Lời giải chi tiết
Migration Plan:
Week 1-2: Setup
├── Deploy API Gateway (Kong/Envoy) trước monolith
├── Route 100% /auth/* traffic qua gateway → monolith
├── Setup distributed tracing (Jaeger)
└── Baseline metrics: latency P50/P95/P99, error rate
Week 3-4: Shadow Mode
├── Deploy Auth Service (Go), kết nối read-replica của user DB
├── Gateway mirror 100% traffic đến cả monolith VÀ Auth Service
├── Compare responses, log mismatches
├── Fix divergences (edge cases, encoding differences)
└── Target: < 0.01% mismatch rate
Week 5-6: Canary Rollout
├── 5% traffic → Auth Service (internal users first)
├── Monitor: error rate, latency, user complaints
├── 25% → 50% → 75% → 100% (mỗi bước chờ 2-3 ngày)
├── Kill switch: 1 command route 100% về monolith
└── Database migration: Auth Service có database riêng
Week 7-8: Cleanup
├── Remove auth code từ monolith
├── Decommission shadow mode
├── Update documentation và runbooks
└── Post-mortem: lessons learnedRollback plan: API Gateway config lưu version trước. Một lệnh kubectl apply -f gateway-rollback.yaml route 100% traffic về monolith trong < 30 giây.
Liên kết học tiếp
Từ khóa glossary: monolith, microservices, modular monolith, strangler fig pattern, service mesh, Conway's Law, team topology, database per service, distributed monolith, API gateway
Tìm kiếm liên quan: kiến trúc hệ thống, so sánh monolith microservices, migration strategy, khi nào dùng microservices