Mới54 bài học mới được bổ sung từ 10/09!
Xem nhật ký cập nhật →
Software Development Atlas
Kiến trúc Phần mềm

Modular Monolith: Ranh giới module trong một deployable

Vận hành modular monolith với public contract, data ownership, dependency guardrail và extraction dựa trên bằng chứng.

Phát triểnĐã xác minh: 11 thg 9, 2026Đánh giá lại: 180 ngày

Bản đồ học tập phát triển phần mềm bởi Tran Trong Thuc · Về dự án Atlas · Cập nhật lần cuối: 22 thg 9, 2026

Modular Monolith: Ranh giới module trong một deployable

Rất nhiều đội ngũ kỹ thuật mắc phải một ảo tưởng chí mạng: họ tin rằng cách duy nhất để cứu vãn mã nguồn hỗn độn (spaghetti code) và lập lại trật tự ranh giới là phải xé nhỏ hệ thống thành hàng chục microservices. Họ vội vã đưa hệ thống lên cụm Kubernetes, cấu hình distributed tracing và rồi lập tức chìm nghỉm trong ma trận độ trễ mạng, giao dịch phân tán và lỗi sập dây chuyền. Nhưng một sự thật tàn nhẫn không bao giờ thay đổi: nếu các kỹ sư không thể duy trì được tính kỷ luật và ranh giới sạch sẽ ngay trong cùng một tiến trình ứng dụng, thì việc ném các ranh giới đó qua đường truyền mạng chỉ tạo ra một thảm họa phân tán—cơn ác mộng "distributed monolith".

Kiến trúc Modular Monolith chính là điểm ngọt ngào (sweet spot) lý tưởng. Nó giữ trọn vẹn sự tinh gọn trong vận hành và tốc độ phát hành của một deployable duy nhất, đồng thời áp đặt kỷ luật thép về ranh giới module thông qua public contract và quyền sở hữu dữ liệu (data ownership) tuyệt đối.

💡 Quy tắc bỏ túi: Nếu bạn không thể giữ vững ranh giới module trong một tiến trình bằng trình biên dịch và architecture test, bạn không bao giờ giữ nổi ranh giới đó qua mạng lưới microservices. Hãy làm chủ quyền sở hữu dữ liệu logic và public contract nghiêm ngặt trong một deployable duy nhất trước; việc tách microservice chỉ trở nên dễ dàng khi ranh giới nội bộ đã hoàn toàn kín kẽ.

Tóm tắt nhanh

  • Một deployable duy nhất với ranh giới module thép: Giữ trọn sự đơn giản khi triển khai và vận hành của một đơn vị triển khai, nhưng phân chia ứng dụng thành các module độc lập tương tác nghiêm ngặt qua public contract.
  • Chủ quyền dữ liệu logic trong database dùng chung: Dù các module có thể dùng chung một database vật lý, mỗi module phải toàn quyền sở hữu bảng và schema của mình. Tuyệt đối cấm đoán việc query chéo module hoặc ghi đè dữ liệu trực tiếp.
  • Tương tác nội bộ không độ trễ mạng: Các module cộng tác qua lời gọi hàm trong process (in-process function call) hoặc sự kiện domain nội bộ, tránh hoàn toàn chi phí serialize dữ liệu và bài toán giao dịch phân tán.
  • Bảo vệ bằng test kiến trúc tự động: Dùng các công cụ kiểm tra kiến trúc (như ArchUnit, dependency-cruiser) để biến ranh giới thành rào chắn code, chặn đứng hoàn toàn việc deep-import vào implementation detail nội bộ.
  • Cạm bẫy chết người: Đi cửa sau qua Database (Database Backdoor)—Biến cơ sở dữ liệu dùng chung thành nơi tích hợp tự do, nơi Module A thản nhiên viết câu lệnh SQL JOIN hoặc ghi đè trực tiếp vào bảng nội bộ của Module B. Lỗi này xóa sổ mọi ranh giới module ngay lập tức và biến mọi nỗ lực tái cấu trúc trong tương lai thành nhiệm vụ bất khả thi.

Module dùng chung process/database vật lý nhưng không nên dùng chung ownership. Module boundary không tự tạo cô lập lỗi, independent scaling hay independent rollout như microservice.

Dependency phải nhìn thấy và kiểm tra được

Chỉ import public entry point; cấm deep import; dependency cycle phải làm architecture test fail.

Orders owns: orders, order_lines
Billing owns: invoices, payment_attempts

Direct database access hoặc query chéo module vào table của owner khác bypass invariant và coupling consumer vào schema nội bộ. Ưu tiên public query/operation hoặc read model được publish có chủ đích.

Sync call, event và transaction

Khi caller cần kết quả ngay, in-process function call là lựa chọn tốt: Checkout -> Pricing.calculateQuote() -> Inventory.reserve().

Shared database cho phép local transaction qua nhiều module, nhưng transaction boundary cần owner rõ. Không cấm cross-module transaction máy móc; hãy document invariant và ảnh hưởng tới future extraction.

Composition và vận hành

Composition root biết concrete implementation; business code không dùng global service locator. Modular monolith cải thiện change locality và ownership nhưng không tạo process-level fault isolation.

Chỉ tách thành microservice khi independent deployment có lợi ích cụ thể: scaling khác biệt, security/availability boundary riêng, fault isolation có giá trị đo được hoặc release cadence độc lập. Trước extraction hãy thu nhỏ contract, bỏ deep import, gán table/schema ownership, bỏ cross-module write, phá cycle và thêm boundary test.

Tình huống production

Hệ thống có folder orders, billing, catalog, nhưng Orders import Billing ORM entity, Billing query trực tiếp table Orders, shared core chứa business rules và một thay đổi pricing chạm nhiều module.

Hậu quả: feature nhỏ cần phối hợp rộng, schema refactor gây regression khó đoán và future service extraction chỉ chuyển hidden coupling qua network.

Nguyên nhân cốt lõi: package name tồn tại nhưng không enforce public contract, private internals, dependency direction hay data ownership.

Cách khắc phục chuẩn: gán capability owner, cấm deep import, route cross-module read/write qua contract, gán table/schema ownership, phá cycle và enforce dependency graph bằng architecture test; sau đó replay thay đổi để đo blast radius.

Tự kiểm tra: mọi module có phải deploy độc lập không?

Không. Modular monolith chủ động giữ một deployment boundary. Independent deployment sẽ đổi topology theo hướng service hoặc microservice.

Checklist production

  • Có một deployable được hiểu rõ.
  • Module theo capability/ownership và public contract nhỏ.
  • Private internals không bị deep-import.
  • Dependency graph được architecture test enforce.
  • Business data có module ownership dù dùng database dùng chung.
  • In-process call/event được chọn theo semantics.
  • Transaction qua nhiều module có owner.
  • Không nhầm module boundary với fault isolation.
  • Extraction cần operational evidence cụ thể.

Quy tắc cho agent

Khi thay đổi modular monolith, xác định module owner trước, đi qua public contract, giữ storage/implementation detail ở private và tăng cường machine-checkable boundary khi phát hiện unauthorized dependency.

Nguồn tham khảo

Mục lục bài học