Chương 1. Học Kiến Trúc Để Làm Gì?
Nhiều người nghe đến "kiến trúc hệ thống" là nghĩ ngay đến microservices, Kafka, Kubernetes, service mesh, API Gateway, sharding, event sourcing. Nhưng đó chỉ là công cụ và pattern. Chúng không phải mục tiêu.
Mục tiêu thật của kiến trúc là giúp hệ thống sống được trong thực tế:
- Chạy ổn khi có người dùng thật.
- Dễ hiểu để tiếp tục phát triển.
- Dễ debug khi có lỗi.
- Dễ mở rộng khi tải tăng.
- Dễ thay đổi khi sản phẩm đổi hướng.
- Không tốn chi phí vận hành vô ích.
Một hệ thống dùng ít công nghệ nhưng chạy ổn, dễ sửa, dễ đo và đủ nhanh thường tốt hơn một hệ thống dùng nhiều công nghệ hiện đại nhưng khó vận hành.
---
1.1. Kiến trúc không phải để dùng nhiều công nghệ
Người mới học kiến trúc rất dễ rơi vào bẫy "càng nhiều thành phần càng chuyên nghiệp":
- Có microservices thì nghe chuyên nghiệp hơn monolith.
- Có Kafka thì nghe scale hơn Redis.
- Có Kubernetes thì nghe hiện đại hơn Docker Compose.
- Có API Gateway thì nghe enterprise hơn reverse proxy.
- Có event-driven thì nghe xịn hơn gọi API trực tiếp.
Nhưng trong thực tế, mỗi thành phần mới đều thêm một loại chi phí:
- Chi phí học.
- Chi phí deploy.
- Chi phí debug.
- Chi phí monitoring.
- Chi phí bảo mật.
- Chi phí phối hợp giữa các phần.
- Chi phí tìm người hiểu hệ thống.
Không có công nghệ nào miễn phí. Một công nghệ chỉ đáng dùng khi vấn đề nó giải quyết lớn hơn độ phức tạp nó mang vào.
Ví dụ:
- Nếu một server xử lý tốt traffic hiện tại, chưa cần load balancer phức tạp.
- Nếu một database vẫn chạy ổn, chưa cần sharding.
- Nếu job nền chỉ vài tác vụ đơn giản, chưa cần Kafka.
- Nếu một monolith vẫn dễ phát triển, chưa cần microservices.
- Nếu deploy mỗi ngày vẫn an toàn, chưa cần Kubernetes.
Điểm trưởng thành trong kiến trúc không phải là biết nhiều công nghệ, mà là biết nói: "Chưa cần."
---
1.2. Kiến trúc là cách kiểm soát độ phức tạp
Phần mềm luôn có xu hướng phức tạp dần:
- Tính năng nhiều hơn.
- Dữ liệu nhiều hơn.
- Người dùng nhiều hơn.
- Nhân sự tham gia nhiều hơn.
- Tích hợp bên ngoài nhiều hơn.
- Lỗi production nhiều hơn.
Nếu không có kiến trúc, hệ thống sẽ biến thành một khối rối:
- Sửa một chỗ hỏng ba chỗ.
- Không ai biết logic nằm ở đâu.
- Query database chậm nhưng không biết vì sao.
- Job chạy lỗi nhưng không biết đang kẹt ở bước nào.
- Service gọi nhau vòng vòng.
- Deploy là một lần cầu may.
Kiến trúc tốt không làm hệ thống hết phức tạp. Nó làm độ phức tạp nằm đúng chỗ.
Ví dụ:
- Logic nghiệp vụ quan trọng nằm trong service/use case rõ ràng.
- Việc dài được đưa ra queue.
- Dữ liệu quan trọng được bảo vệ bằng transaction.
- Giao tiếp giữa service có timeout và retry.
- Logs có correlation ID để lần theo lỗi.
- Module có ranh giới để không import chéo lung tung.
Kiến trúc là cách biến sự phức tạp hỗn loạn thành sự phức tạp có tổ chức.
---
1.3. Hệ thống tốt là hệ thống đủ đơn giản để vận hành
Một hệ thống không chỉ cần viết được. Nó còn cần vận hành được.
Vận hành nghĩa là:
- Biết hệ thống đang sống hay chết.
- Biết request đang chậm ở đâu.
- Biết queue có đang kẹt không.
- Biết database có quá tải không.
- Biết deploy mới có làm lỗi tăng không.
- Biết rollback nếu có vấn đề.
- Biết backup có restore được không.
Nhiều kiến trúc nhìn rất đẹp trên sơ đồ nhưng vận hành rất mệt:
- Quá nhiều service nhỏ.
- Quá nhiều queue.
- Quá nhiều database.
- Quá nhiều deployment pipeline.
- Quá nhiều tầng proxy/gateway.
- Quá ít monitoring.
Sơ đồ càng nhiều hộp, hệ thống càng cần nhiều quan sát. Nếu không có observability tương ứng, kiến trúc phức tạp sẽ trở thành bóng tối.
Nguyên tắc thực dụng:
> Đừng thêm một thành phần mà bạn chưa biết cách quan sát, debug và khôi phục khi nó hỏng.
---
1.4. Công nghệ chỉ có ý nghĩa khi gắn với vấn đề thật
Cùng một công nghệ có thể rất đúng trong hệ thống này và rất sai trong hệ thống khác.
Redis
Redis có thể dùng để:
- Cache.
- Counter.
- Distributed lock.
- Queue nhẹ.
- Pub/Sub.
- Rate limit.
Nhưng Redis không tự động làm hệ thống tốt hơn. Nếu cache sai dữ liệu, hệ thống sẽ trả kết quả cũ. Nếu dùng Pub/Sub cho job quan trọng mà subscriber chết, message có thể mất. Nếu dùng Redis làm lock nhưng không hiểu timeout, có thể tạo lỗi cạnh tranh khó debug.
Kafka
Kafka rất mạnh khi cần:
- Event log bền vững.
- Replay dữ liệu.
- Nhiều consumer độc lập.
- Stream processing.
- Data pipeline lớn.
Nhưng nếu chỉ cần gửi vài job nền, Kafka có thể là quá mức. Bạn sẽ phải hiểu partition, offset, consumer group, retention, schema evolution, monitoring. Nếu chưa cần những thứ đó, RabbitMQ, Redis Streams, SQS hoặc Celery broker có thể thực dụng hơn.
Kubernetes
Kubernetes mạnh khi cần:
- Chạy nhiều container.
- Tự động restart.
- Scale service.
- Rolling deploy.
- Service discovery.
- Chuẩn hóa hạ tầng cho nhiều team.
Nhưng Kubernetes không sửa code chậm, không sửa query tệ, không tự làm queue bền hơn, không làm app có observability. Nếu hệ thống còn nhỏ, PaaS, VPS, Docker Compose hoặc managed container service có thể hợp lý hơn.
Vì vậy, câu hỏi đúng không phải là:
> Công nghệ này có xịn không?
Mà là:
> Vấn đề của mình có đúng là vấn đề mà công nghệ này giải quyết không?
---
1.5. Kiến trúc tốt giúp ta trả lời các câu hỏi khó
Một người làm kiến trúc không nhất thiết phải biết hết mọi framework. Nhưng họ cần biết cách trả lời các câu hỏi như:
- Nếu lượng request tăng 10 lần, hệ thống nghẽn ở đâu?
- Nếu API bên thứ ba chậm 60 giây, hệ thống có bị giữ tài nguyên không?
- Nếu worker chết giữa chừng, job có mất không?
- Nếu retry xảy ra 3 lần, dữ liệu có bị ghi trùng không?
- Nếu cache trả dữ liệu cũ, nghiệp vụ có chấp nhận không?
- Nếu service A chết, service B có chết theo không?
- Nếu database chính hỏng, khôi phục mất bao lâu?
- Nếu deploy lỗi, rollback thế nào?
- Nếu tách service này, dữ liệu nào thuộc về ai?
Những câu hỏi này không phải cú pháp. AI có thể viết code, nhưng quyết định kiến trúc vẫn cần người hiểu bối cảnh, trade-off và hậu quả.
---
1.6. Kiến trúc là trade-off, không phải đáp án tuyệt đối
Không có lựa chọn nào chỉ có lợi.
Monolith
Lợi:
- Dễ phát triển lúc đầu.
- Dễ transaction.
- Dễ deploy.
- Dễ debug hơn hệ phân tán.
Giá phải trả:
- Khi lớn có thể khó chia team.
- Một phần lỗi có thể ảnh hưởng toàn hệ thống.
- Scale theo từng phần khó hơn.
Microservices
Lợi:
- Tách deploy.
- Tách scale.
- Tách failure boundary.
- Tách ownership.
Giá phải trả:
- Network phức tạp.
- Dữ liệu phân tán.
- Debug khó hơn.
- Cần observability tốt.
- Cần contract và versioning.
Cache
Lợi:
- Đọc nhanh hơn.
- Giảm tải database.
Giá phải trả:
- Dữ liệu có thể cũ.
- Invalidation khó.
- Thêm một nơi có thể lỗi.
Queue
Lợi:
- Request trả nhanh hơn.
- Chịu được burst traffic.
- Retry được job lỗi.
Giá phải trả:
- Hệ thống bất đồng bộ hơn.
- Cần quản lý trạng thái job.
- Cần monitoring queue.
- Có thể xử lý trùng nếu không idempotent.
Kiến trúc là nghệ thuật chọn cái giá mình sẵn sàng trả.
---
1.7. Cách đọc một quyết định kiến trúc
Khi thấy một hệ thống dùng công nghệ hoặc pattern nào đó, đừng vội kết luận nó đúng hay sai. Hãy đọc theo khung sau.
1. Vấn đề là gì?
Ví dụ:
- Request chậm.
- Database quá tải.
- Job bị kẹt.
- Deploy khó.
- Team dẫm chân nhau.
- Dữ liệu cần phân tích.
- Cần realtime.
Nếu không biết vấn đề, không thể đánh giá giải pháp.
2. Vì sao giải pháp này được chọn?
Ví dụ:
- Dùng queue để không giữ request.
- Dùng cache để giảm query.
- Dùng CDN để giảm tải file tĩnh.
- Dùng microservice để scale phần xử lý nặng riêng.
- Dùng Kafka để replay event.
3. Có lựa chọn đơn giản hơn không?
Đây là câu hỏi rất quan trọng.
- Có cần Kafka hay RabbitMQ đủ?
- Có cần Kubernetes hay PaaS đủ?
- Có cần microservice hay modular monolith đủ?
- Có cần WebSocket hay polling đủ?
- Có cần NoSQL hay PostgreSQL đủ?
4. Cái giá phải trả là gì?
Mỗi quyết định phải được nhìn cùng cái giá:
- Vận hành khó hơn?
- Debug khó hơn?
- Chi phí cloud cao hơn?
- Dữ liệu nhất quán chậm hơn?
- Cần thêm monitoring?
- Cần thêm người có kỹ năng?
5. Làm sao biết nó hiệu quả?
Một quyết định kiến trúc nên có cách đo:
- Latency giảm không?
- Throughput tăng không?
- Queue age giảm không?
- Error rate giảm không?
- Deploy an toàn hơn không?
- Cost có chấp nhận được không?
Nếu không đo được hiệu quả, rất dễ chỉ đang làm hệ thống phức tạp hơn.
---
1.8. Các cấp độ trưởng thành trong tư duy kiến trúc
Cấp 1: Chọn theo trend
"Nghe nói Kafka scale tốt nên dùng Kafka." "Công ty lớn dùng Kubernetes nên mình cũng dùng." "Microservices là kiến trúc hiện đại nên phải tách."
Ở cấp này, công nghệ dẫn dắt quyết định.
Cấp 2: Chọn theo quen tay
"Team quen Django nên cái gì cũng Django." "Team quen Node nên cái gì cũng Node." "Trước giờ dùng Redis nên cứ dùng Redis."
Ở cấp này, kinh nghiệm giúp đi nhanh nhưng có thể giới hạn góc nhìn.
Cấp 3: Chọn theo vấn đề
"Tác vụ này dài, cần queue." "Dữ liệu này đọc nhiều, có thể cache." "Phần này cần scale riêng, có thể tách service." "Luồng này cần audit tuyệt đối, không được chỉ lưu balance hiện tại."
Ở cấp này, vấn đề dẫn dắt công nghệ.
Cấp 4: Chọn theo trade-off và giai đoạn
"Hiện tại monolith đủ, nhưng code theo modular monolith để 6 tháng nữa có thể tách." "Redis đủ cho queue hiện tại, Kafka để sau khi cần replay event." "Polling đủ cho MVP, WebSocket để sau nếu realtime trở thành tính năng lõi." "Chưa dùng Kubernetes vì team chưa có năng lực vận hành nó."
Ở cấp này, người thiết kế không chỉ biết giải pháp, mà còn biết thời điểm.
---
1.9. Những điều kiến trúc không thay thế được
Kiến trúc tốt không cứu được mọi thứ.
Nó không thay thế:
- Product direction sai.
- Logic nghiệp vụ sai.
- Code cẩu thả.
- Không có test.
- Không có monitoring.
- Không có backup.
- Không hiểu người dùng.
- Không hiểu dữ liệu.
Một hệ thống có kiến trúc đẹp nhưng không giải quyết đúng vấn đề người dùng vẫn là hệ thống thất bại.
Ngược lại, một hệ thống đơn giản nhưng giải quyết đúng vấn đề, có số liệu, có khả năng tiến hóa, thường là nền móng tốt hơn nhiều.
---
1.10. Kết luận của chương
Học kiến trúc không phải để thuộc nhiều tên công nghệ. Học kiến trúc là để có khả năng nhìn một hệ thống và trả lời:
- Nó đang đơn giản hay rối?
- Nó đang nghẽn ở đâu?
- Phần nào nên xử lý ngay, phần nào nên đưa ra nền?
- Dữ liệu nào cần đúng ngay, dữ liệu nào có thể đồng bộ sau?
- Khi nào giữ monolith, khi nào tách service?
- Khi nào dùng cache, queue, Pub/Sub, Kafka?
- Khi nào nên mua managed service, khi nào tự host?
- Nếu hệ thống hỏng, ta có biết nhìn vào đâu không?
Kiến trúc tốt là sự tỉnh táo trước độ phức tạp. Nó không chạy theo hình thức, không thần thánh công nghệ, và không tối ưu sớm. Nó giúp ta xây hệ thống vừa đủ cho hiện tại, nhưng không đóng cửa tương lai.