Trong nhiều năm qua, ngành công nghiệp phần mềm đã bị tẩy não bởi một định kiến: “Modular Monolith chỉ là bước đệm yếu kém trước khi hệ thống đủ lớn để tiến lên Microservices”. Rất nhiều công ty, dù quy mô kỹ sư chỉ đếm trên đầu ngón tay, vẫn vội vã đập bỏ kiến trúc nguyên khối để chạy theo “đám mây” phân tán.

Họ gọi đó là Tương Lai. Kiến trúc sư Rico Fritzsche gọi đó là “CV-Driven Development” (Lập trình để làm đẹp hồ sơ) trong bài viết nổi tiếng của ông trên GitConnected. Và dữ liệu thực tế năm 2025 đang chứng minh Rico đúng.


1. Sự Thoái Trào Của Microservices (Dữ Liệu Thực Chứng)

Theo khảo sát CNCF năm 2025, 42% các tổ chức từng áp dụng Microservices đã phải gộp (consolidate) hệ thống trở lại thành Modular Monolith do chạm tới “Trần Điều Phối” (Coordination Ceiling), nơi chi phí vận hành bóp nghẹt lợi ích kỹ thuật.

Nếu bạn vẫn nghĩ Microservices luôn là đỉnh cao kiến trúc, hãy nhìn vào những “cú quay xe” kinh điển nhất trong ngành:

Amazon Prime Video: Tiết Kiệm 90% Chi Phí

Đầu năm 2023, Amazon Prime Video gây chấn động khi từ bỏ kiến trúc Serverless Microservices (AWS Lambda/Step Functions) cho hệ thống giám sát Video. Lý do? Hàng triệu lệnh chuyển trạng thái (state transitions) và việc truyền dữ liệu liên tục qua mạng (S3) tạo ra độ trễ khổng lồ và hóa đơn điện toán cắt cổ. Bằng cách gộp tất cả về một Monolith chạy trên EC2/ECS, họ đã giảm 90% chi phí hạ tầng.

Segment: Tạm Biệt 140 Microservices

Segment từng cắt nhỏ hệ thống thành 140 Microservices. Thay vì code tính năng mới, đội ngũ kỹ sư phải trở thành “thợ ống nước” — cấu hình Service Mesh, quản lý dependency chéo, và tuyệt vọng trong việc trace bug qua hàng chục repository khác nhau (Cognitive Load). Khi gom tất cả lại thành một Modular Monolith, tốc độ phát triển của họ tăng vọt trở lại.


2. Giải Phẫu Bẫy “Distributed Monolith”

Distributed Monolith (Monolith phân tán) là một Anti-pattern thảm họa, nơi hệ thống bị cắt nhỏ thành nhiều Services nhưng vẫn phụ thuộc chặt chẽ vào nhau (Tightly Coupled), dẫn đến việc thừa hưởng mọi nhược điểm của Microservices mà không có ưu điểm nào.

Bạn chia hệ thống E-commerce thành OrderService, PaymentService, và InventoryService. Khi có một đơn hàng mới, OrderService gọi gRPC sang Payment, chờ Payment gọi HTTP sang Inventory.

graph TD
    subgraph Distributed_Monolith ["The Distributed Monolith Anti-Pattern"]
        Order[Order Service]
        Payment[Payment Service]
        Inventory[Inventory Service]
        
        Order -- HTTP / Tightly Coupled --> Payment
        Payment -- gRPC / Tightly Coupled --> Inventory
        Inventory -- Timeout / Fail --> Payment
        Payment -- Cascading Failure --> Order
    end
    
    style Order fill:#f9c,stroke:#333
    style Payment fill:#f9c,stroke:#333
    style Inventory fill:#f9c,stroke:#333
  • The Distributed Penalty (Hình phạt phân tán): Một lời gọi hàm trong RAM (In-memory function call) tốn khoảng vài nanoseconds. Một lời gọi mạng giữa 2 services mất vài milliseconds (chậm hơn hàng triệu lần).
  • Không thể Deploy độc lập (đổi Data Schema ở Order thì Payment sập).
  • Transaction phức tạp (Phải dùng Saga Pattern, 2PC).

3. Tại Sao Golang Sinh Ra Để Làm Modular Monolith?

Golang cung cấp các cơ chế phân lập code (như internal packages) và giao tiếp trong bộ nhớ (Go Channels, Interfaces) vô cùng mạnh mẽ, biến nó thành ngôn ngữ hoàn hảo nhất để thiết kế Modular Monolith với độ trễ (Latency) cực thấp.

Thay vì dùng Kubernetes và gRPC để cách ly các module, Golang cho phép bạn làm điều đó ngay ở mức Compiler.

Cô lập Domain bằng internal package

Go có một tính năng cực kỳ tinh tế: thư mục internal/. Code nằm trong internal/ chỉ có thể được truy cập bởi các package có cùng cha. Bạn có thể bẻ gãy sự “Tightly Coupled” mà không cần phải tách ra thành repository riêng biệt.

project-root/
├── cmd/
   └── api/
       └── main.go (Điểm khởi chạy duy nhất - 1 Binary)
├── internal/
   ├── order/
      ├── handler.go
      └── service.go (Chỉ giao tiếp với payment qua Interface)
   ├── payment/
      └── service.go

In-Memory Communication

Thay vì để Order bắn HTTP Request sang Payment, chúng giao tiếp qua Interface (Dependency Injection) hoặc bắn Event qua Go Channels. Mọi thứ diễn ra bên trong 1 Process, 1 vùng RAM. Tốc độ là Nanoseconds. Ngay cả khi sau này thực sự cần tách rời, bạn nên tham khảo Kiến trúc Event-Driven Microservices với NATS để tránh giao tiếp HTTP đồng bộ.


4. Định Luật Conway: Khi Nào Mới Cần Microservices?

Định luật Conway khẳng định: “Cấu trúc phần mềm phản chiếu cấu trúc giao tiếp của tổ chức tạo ra nó.”

Chỉ áp dụng Microservices khi bạn thỏa mãn các điều kiện sau:

  1. Ranh giới tổ chức (Organizational Scale): Bạn có >50 Backend Dev, chia thành các team “2-pizza” (5-7 người) độc lập. Việc merge chung 1 repo gây ra Conflict không thể giải quyết bằng CI/CD.
  2. Nhu cầu Scale hạ tầng bất đối xứng: Ví dụ, tính năng “Video Encode” cần GPU và ngốn 90% CPU, trong khi tính năng “User Profile” chỉ cần RAM. Lúc này, tách VideoService ra riêng là có ý nghĩa thực tiễn.

Câu Hỏi Thường Gặp (FAQ)

1. “Coordination Ceiling” (Trần Điều Phối) là gì? Là điểm giới hạn mà tại đó, chi phí vận hành hệ thống phân tán (Network latency, Data consistency, CI/CD pipeline) chính thức vượt qua các lợi ích của việc có thể Deploy độc lập. Vượt qua điểm này, Microservices trở thành gánh nặng tài chính.

2. Làm sao để scale một Modular Monolith? Scale theo chiều ngang (Horizontal Scaling)! Bạn hoàn toàn có thể chạy 50 container (hoặc EC2 instances) chứa cùng một mã nguồn Modular Monolith phía sau một Load Balancer. Không cần Microservices mới có thể scale ngang.

3. Làm sao để tránh việc các Module dính chùm vào nhau (Spaghetti code) trong Monolith? Hãy áp dụng Kiến trúc Lục giác (Hexagonal Architecture) hoặc Clean Architecture. Sử dụng Dependency Injection và ép các module chỉ được phép giao tiếp với nhau qua Interfaces. Nếu viết bằng Golang, hãy sử dụng triệt để internal/ packages để Compiler tự động chặn các truy cập trái phép xuyên module.


Tạm kết

Modular Monolith không phải là “người tiền nhiệm” lạc hậu của Microservices. Nó là một đích đến hoàn chỉnh. Hãy xây dựng một Modular Monolith sạch sẽ, phân định Domain rõ ràng. Chỉ cắt nó ra khi — và chỉ khi — chi phí giao tiếp giữa các con người (Developers) cao hơn chi phí giao tiếp qua Mạng lưới (Network).