Chương 9. So Sánh Monolith, Modular Monolith Và Microservices

Ba khái niệm này rất dễ bị trộn lẫn:

  • Monolith
  • Modular monolith
  • Microservices

Nhiều người nói "monolith" để chỉ code rối, nói "microservices" để chỉ hệ thống xịn, và quên mất modular monolith là một lựa chọn trung gian rất mạnh.

Chương này đặt ba lựa chọn lên cùng một bàn cân. Không có lựa chọn nào luôn đúng. Mỗi lựa chọn phù hợp với một giai đoạn, một loại team, một mức tải và một mức độ phức tạp khác nhau.

---

9.1. Ví dụ ba mô hình quán bánh

Hãy dùng lại ví dụ quán bánh.

Monolith: một cửa hàng, ai cũng làm nhiều việc

Quán nhỏ, ít người:

  • Một nơi nhận đơn.
  • Một bếp.
  • Một sổ đơn hàng.
  • Một nhóm người làm chung.

Mọi thứ gần nhau. Cần hỏi gì thì quay sang hỏi. Cần sửa gì thì sửa ngay.

Rất nhanh lúc đầu, nhưng nếu không tổ chức tốt, dễ rối.

Modular monolith: một cửa hàng, chia bộ phận rõ

Quán vẫn là một cửa hàng, nhưng có bộ phận:

  • Nhận đơn.
  • Bếp.
  • Giao hàng.
  • Thu tiền.
  • Kế toán.
  • Chăm sóc khách.

Vẫn cùng một nơi vận hành, nhưng mỗi bộ phận có trách nhiệm rõ. Người nhận đơn không tự ý sửa sổ kế toán. Bếp không tự ý đổi trạng thái thanh toán.

Đây là một cửa hàng có tổ chức.

Microservices: chuỗi vận hành nhiều đơn vị riêng

Quán phát triển thành chuỗi:

  • App đặt hàng riêng.
  • Bếp trung tâm riêng.
  • Kho riêng.
  • Giao hàng riêng.
  • Kế toán riêng.
  • Chăm sóc khách riêng.

Mỗi đơn vị có hệ thống, quy trình, người phụ trách riêng. Họ giao tiếp bằng phiếu, API, sự kiện, hợp đồng vận hành.

Điều này cần thiết khi quy mô lớn, nhưng quá nặng nếu quán còn nhỏ.

---

9.2. Tóm tắt ba mô hình

Monolith

Một ứng dụng deploy như một khối. Code có thể đơn giản hoặc rối tùy cách tổ chức.

Browser -> App -> Database

Phù hợp khi:

  • Team nhỏ.
  • Domain còn thay đổi.
  • Sản phẩm cần đi nhanh.
  • Tải chưa quá khác biệt giữa các phần.

Modular monolith

Vẫn là một ứng dụng deploy chung, nhưng chia module theo nghiệp vụ rõ ràng.

Browser -> App
             |-- users
             |-- catalog
             |-- orders
             |-- payment
             |-- notification
          -> Database

Phù hợp khi:

  • Codebase bắt đầu lớn.
  • Cần ranh giới nhưng chưa muốn trả giá microservices.
  • Muốn chuẩn bị khả năng tách service sau này.

Microservices

Hệ thống chia thành nhiều service chạy riêng, deploy riêng, giao tiếp qua network/message.

Browser -> API Gateway
              |-- User Service
              |-- Catalog Service
              |-- Order Service
              |-- Payment Service
              |-- Notification Service

Phù hợp khi:

  • Có nhiều team/service owner.
  • Cần scale/deploy/failure isolation riêng.
  • Domain boundary rõ.
  • Có năng lực vận hành hệ phân tán.

---

9.3. Khác nhau về code

Monolith

Code nằm trong một ứng dụng. Nếu không có kỷ luật, dễ thành:

views.py
models.py
services.py
utils.py

Mọi thứ gọi nhau lung tung.

Nhưng monolith không bắt buộc phải rối. Nó chỉ dễ rối nếu team không tổ chức.

Modular monolith

Code chia theo domain:

users/
catalog/
orders/
payments/
notifications/

Mỗi module có trách nhiệm rõ.

Module khác gọi qua interface/service, không chọc sâu vào nội bộ.

Microservices

Code nằm ở nhiều service riêng:

user-service/
catalog-service/
order-service/
payment-service/
notification-service/

Mỗi service có repository riêng hoặc thư mục riêng trong monorepo, có pipeline/deploy riêng.

Code độc lập hơn, nhưng việc thay đổi một flow xuyên nhiều service phức tạp hơn.

Tư duy

Nếu vấn đề là code rối, modular hóa trước. Đừng vội tách service. Microservices không tự làm code sạch.

---

9.4. Khác nhau về database

Monolith

Thường dùng một database chung.

Lợi:

  • Query dễ.
  • Transaction dễ.
  • Constraint rõ.
  • Ít đồng bộ dữ liệu.

Hại:

  • Dễ join chéo lung tung.
  • Dữ liệu không có owner rõ.
  • Khó tách sau này nếu không có kỷ luật.

Modular monolith

Vẫn có thể dùng chung database, nhưng cố gắng có ownership theo module.

Ví dụ:

  • orders sở hữu bảng order.
  • payments sở hữu bảng payment.
  • catalog sở hữu bảng product.

Không nhất thiết cấm mọi foreign key chéo, nhưng phải hiểu coupling.

Microservices

Lý tưởng là mỗi service sở hữu dữ liệu riêng.

Order Service -> Order DB
Payment Service -> Payment DB
Catalog Service -> Catalog DB

Lợi:

  • Boundary dữ liệu rõ.
  • Service có thể thay đổi schema riêng.
  • Giảm phụ thuộc trực tiếp.

Hại:

  • Không join trực tiếp dễ dàng.
  • Transaction phân tán khó.
  • Cần event, read model, API composition.
  • Dữ liệu có thể eventual consistency.

Tư duy

Database per service là một trong những phần khó nhất của microservices. Nếu chưa sẵn sàng trả giá này, đừng vội tách service sâu.

---

9.5. Khác nhau về deploy

Monolith

Một app, một deploy.

Lợi:

  • Pipeline đơn giản.
  • Rollback dễ hơn.
  • Ít version phối hợp.

Hại:

  • Một thay đổi nhỏ cũng deploy cả app.
  • Nếu app lớn, deploy có thể chậm/rủi ro.

Modular monolith

Vẫn deploy chung.

Điểm khác là code bên trong rõ hơn, nên rủi ro thay đổi thấp hơn nếu module boundary tốt.

Microservices

Mỗi service có thể deploy riêng.

Lợi:

  • Team deploy độc lập.
  • Hotfix một service không cần deploy toàn hệ thống.
  • Service thay đổi nhanh có thể release riêng.

Hại:

  • Nhiều pipeline.
  • Nhiều version cùng tồn tại.
  • Cần backward compatibility.
  • Cần contract testing.
  • Rollback phức tạp hơn nếu flow liên quan nhiều service.

Tư duy

Deploy độc lập chỉ có giá trị khi service thật sự độc lập. Nếu mỗi deploy vẫn phải điều phối 5 service cùng lúc, hệ thống chưa đạt lợi ích microservices.

---

9.6. Khác nhau về giao tiếp

Monolith

Các phần gọi nhau bằng function/class trong cùng process.

order_service.place_order()

Nhanh, ít lỗi network, dễ trace bằng stack trace.

Modular monolith

Vẫn gọi hàm nội bộ, nhưng qua interface rõ.

payment_module.create_payment(...)

Khác monolith rối ở chỗ không import nội bộ lung tung.

Microservices

Giao tiếp qua network hoặc broker:

HTTP/gRPC
Message queue
Pub/Sub
Event streaming

Cần quan tâm:

  • Timeout.
  • Retry.
  • Idempotency.
  • Network failure.
  • Contract.
  • Versioning.
  • Observability.

Tư duy

Gọi hàm không xấu nếu đang trong cùng app. Đừng tự tạo HTTP nội bộ chỉ để giống microservices.

---

9.7. Khác nhau về lỗi và failure mode

Monolith

Một lỗi nghiêm trọng có thể ảnh hưởng toàn app.

Ví dụ:

  • Memory leak trong app.
  • CPU-bound job chạy trong web process.
  • External API không timeout giữ hết worker.

Nhưng vì ít thành phần, lỗi thường dễ lần hơn nếu có log tốt.

Modular monolith

Tốt hơn monolith rối vì module boundary rõ, việc dài có thể tách queue/worker.

Vẫn cần cẩn thận vì deploy/runtime chung.

Microservices

Một service chết có thể không làm chết toàn hệ thống nếu thiết kế tốt.

Nhưng cũng có thể gây chết dây chuyền nếu:

  • Không timeout.
  • Retry liên tục.
  • Không fallback.
  • Service phụ thuộc vòng vòng.
  • Connection pool cạn.

Tư duy

Microservices không tự tạo failure isolation. Nó chỉ tạo cơ hội để cách ly lỗi. Muốn cách ly thật phải thiết kế timeout, retry, circuit breaker, fallback, queue và observability.

---

9.8. Khác nhau về debug

Monolith

Debug thường dễ hơn:

  • Một codebase.
  • Một process chính.
  • Một database.
  • Stack trace rõ hơn.

Nhưng nếu code rối, vẫn rất đau.

Modular monolith

Debug tốt hơn monolith rối vì biết lỗi thuộc module nào.

Nếu có logs theo use case/module, việc lần lỗi khá rõ.

Microservices

Debug khó hơn vì request đi qua nhiều service.

Cần:

  • Correlation ID.
  • Centralized logging.
  • Distributed tracing.
  • Metrics từng service.
  • Dashboard dependency.

Không có những thứ này, debug microservices rất cực.

Tư duy

Nếu chưa có observability tốt, đừng vội tăng số service. Càng nhiều service, càng cần ánh sáng.

---

9.9. Khác nhau về tốc độ phát triển

Giai đoạn đầu

Monolith hoặc modular monolith thường nhanh hơn.

Lý do:

  • Ít hạ tầng.
  • Ít API contract.
  • Ít distributed transaction.
  • Ít deploy pipeline.
  • Dễ thay đổi domain.

Khi team lớn hơn

Microservices có thể giúp nếu:

  • Team chia theo service/domain.
  • Mỗi service có ownership rõ.
  • Deploy độc lập thật sự.
  • Contract rõ.

Nếu không, microservices có thể làm chậm hơn vì mọi thay đổi phải đi qua nhiều service.

Tư duy

Tốc độ phát triển không chỉ phụ thuộc code. Nó phụ thuộc chi phí phối hợp. Monolith giảm chi phí hạ tầng. Microservices giảm chi phí phối hợp giữa team lớn, nhưng chỉ khi boundary đúng.

---

9.10. Khác nhau về scale

Monolith

Thường scale cả app:

App instance x 5

Nếu mọi phần có tải tương đối giống nhau, cách này ổn.

Modular monolith

Vẫn scale app, nhưng có thể tách worker theo workload:

Web app x 3
AI worker x 10
Email worker x 5
Report worker x 1

Đây là bước rất thực dụng trước microservices.

Microservices

Scale từng service:

Catalog Service x 10
Payment Service x 3
Search Service x 6
Notification Service x 20

Lợi nếu workload khác nhau rõ.

Hại nếu service quá nhỏ hoặc gọi nhau quá nhiều.

Tư duy

Nếu phần cần scale là job nền, có thể chỉ cần tách queue/worker trước. Không phải cứ cần scale là microservices ngay.

---

9.11. Khác nhau về chi phí vận hành

Monolith

Ít thành phần hơn:

  • Ít server/container.
  • Ít pipeline.
  • Ít dashboard.
  • Ít network security.
  • Ít secret.

Chi phí vận hành thấp hơn.

Modular monolith

Tăng một chút chi phí tổ chức code, nhưng runtime vẫn đơn giản.

Đây là điểm cân bằng tốt cho nhiều team.

Microservices

Tăng chi phí vận hành:

  • Nhiều service.
  • Nhiều database/queue/cache.
  • Nhiều pipeline.
  • Nhiều log stream.
  • Nhiều alert.
  • Nhiều network policy.
  • Nhiều version API.

Microservices cần năng lực platform/DevOps/SRE tốt hơn.

Tư duy

Không chỉ hỏi "hệ thống có scale không", mà hỏi "team có vận hành nổi không".

---

9.12. Bảng so sánh tổng quát

| Tiêu chí | Monolith | Modular Monolith | Microservices | |---|---|---|---| | Deploy | Chung | Chung | Riêng từng service | | Code boundary | Có thể mờ | Rõ hơn | Rõ nếu thiết kế đúng | | Database | Thường chung | Chung nhưng có ownership | Thường riêng theo service | | Transaction | Dễ | Dễ vừa phải | Khó hơn | | Debug | Dễ hơn nếu code rõ | Tốt | Khó nếu thiếu tracing | | Scale | Scale cả app | Scale app + worker riêng | Scale từng service | | Vận hành | Đơn giản | Vẫn đơn giản | Phức tạp | | Tốc độ ban đầu | Nhanh | Nhanh và bền hơn | Chậm hơn nếu quá sớm | | Phù hợp | Giai đoạn đầu | Sản phẩm nhỏ-vừa/lớn vừa | Hệ lớn, team nhiều, boundary rõ | | Rủi ro chính | Code rối | Kỷ luật module không đủ | Distributed complexity |

---

9.13. Chọn theo giai đoạn sản phẩm

Prototype

Ưu tiên:

  • Làm nhanh.
  • Dễ đổi.
  • Ít hạ tầng.

Thường chọn:

Monolith đơn giản

MVP

Ưu tiên:

  • Ra sản phẩm thật.
  • Có database tốt.
  • Có queue cho việc dài.
  • Có logging cơ bản.

Thường chọn:

Monolith hoặc modular monolith nhẹ

Có khách hàng thật

Ưu tiên:

  • Code không rối.
  • Job nền rõ.
  • Observability tốt hơn.
  • Module boundary rõ.

Thường chọn:

Modular monolith

Scale-up

Ưu tiên:

  • Tách workload rõ.
  • Tách service khi có bottleneck/ownership/deploy riêng.
  • Contract và monitoring tốt.

Thường chọn:

Modular monolith + một số service/worker riêng

Enterprise/lớn

Ưu tiên:

  • Nhiều team.
  • Ownership rõ.
  • Deploy độc lập.
  • Reliability.
  • Security boundary.

Có thể chọn:

Microservices có kiểm soát

---

9.14. Chọn theo quy mô team

1-5 người

Microservices thường quá sớm.

Nên:

  • Monolith rõ.
  • Modular hóa vừa đủ.
  • Queue cho việc dài.
  • Managed service nếu cần giảm vận hành.

5-20 người

Modular monolith rất hợp.

Nên:

  • Chia module theo domain.
  • Service layer rõ.
  • Job queue rõ.
  • Observability tốt.
  • Có thể tách 1-2 service nếu có lý do mạnh.

20+ người

Bắt đầu cần ownership rõ hơn.

Có thể:

  • Tách service theo domain rõ.
  • API contract.
  • CI/CD chuẩn.
  • Tracing/log tập trung.
  • Platform tooling.

Nhiều team độc lập

Microservices có thể đáng giá nếu tổ chức đủ trưởng thành.

Nhưng vẫn cần tránh tách quá nhỏ.

---

9.15. Chọn theo loại bottleneck

Code rối

Giải pháp đầu tiên:

Refactor -> modular monolith

Không phải microservices ngay.

Request chậm vì việc lâu

Giải pháp đầu tiên:

Queue + worker

Đọc database nhiều

Giải pháp có thể:

Index -> query optimization -> cache -> read replica

File/media nặng

Giải pháp:

Object storage + CDN + worker xử lý media

Một workload cần scale riêng

Giải pháp:

Worker/service riêng cho workload đó

Nhiều team cần deploy độc lập

Giải pháp có thể:

Microservices theo domain

---

9.16. Đừng chọn theo cảm giác "xịn"

Một số câu hỏi sai:

  • Monolith có lỗi thời không?
  • Microservices có xịn hơn không?
  • Công ty lớn dùng gì?
  • Framework nào scale nhất?

Câu hỏi đúng:

  • Vấn đề hiện tại là gì?
  • Bottleneck nằm ở đâu?
  • Team có bao nhiêu người?
  • Domain đã rõ chưa?
  • Có cần deploy độc lập không?
  • Có cần scale độc lập không?
  • Có observability đủ chưa?
  • Có trả được chi phí vận hành không?

Kiến trúc tốt không phải kiến trúc nghe sang nhất. Kiến trúc tốt là kiến trúc phù hợp nhất với áp lực thật.

---

9.17. Kết luận của chương

Ba mô hình có thể được hiểu đơn giản:

  • Monolith: một khối deploy chung, tốt khi cần đơn giản và đi nhanh.
  • Modular monolith: vẫn deploy chung, nhưng code chia module rõ, tốt cho phần lớn sản phẩm đang lớn dần.
  • Microservices: nhiều service deploy riêng, tốt khi cần scale/deploy/failure/ownership độc lập và có năng lực vận hành.

Con đường thực dụng thường là:

Monolith
  -> Modular Monolith
  -> Tách worker/read model/service riêng khi có áp lực
  -> Microservices có kiểm soát

Điều quan trọng không phải chọn phe monolith hay microservices. Điều quan trọng là hiểu hệ thống đang ở giai đoạn nào, đang đau ở đâu, và cái giá nào đáng trả.