Monolith · Modular · Microservices

Kiến trúc · một app thương mại điện tử, ba cách xâyArchitecture · one e-commerce app, three ways to build it

Modular monolith trước, microservices khi thấy đau.Modular monolith first, microservices when it hurts.

Ba kiểu kiến trúc chỉ khác nhau trên ba trục: đơn vị triển khai, ranh giới module và quyền sở hữu dữ liệu. Monolith truyền thống và modular monolith chỉ khác nhau đúng một trục — ranh giới có được công cụ ép hay không. Microservices đẩy cả ba trục về phía tách rời, và trả giá bằng mạng.The three styles differ on just three axes: deployment unit, module boundary and data ownership. The traditional and modular monoliths differ on exactly one — whether boundaries are enforced by tooling. Microservices push all three axes toward separation, and pay for it with the network.

Ba bản vẽ mặt bằng của cùng một appThree floor plans of the same app

Cùng ba mảng nghiệp vụ Orders, Payments, Inventory. Viền đậm là ranh giới triển khai, hình trụ là database, đường nối là lời gọi giữa các phần.The same three business areas: Orders, Payments, Inventory. Thick outlines are deployment boundaries, cylinders are databases, lines are calls between parts.

Traditional monolith: one house, no walls, tangled calls, one database controllers/ · services/ · repositories/ Orders Inventory Payments 1 DB
Monolith truyền thốngTraditional monolith Một nhà, không tường. Ai cũng gọi thẳng vào bất cứ ai.One house, no walls. Anything calls anything directly.
Modular monolith: one house split into walled rooms that talk only through doors, one database with a schema per module Orders Payments Inventory internal/ internal/ internal/ api api schema×3
Modular monolith Một nhà, chia phòng có tường. Chỉ đi qua cửa — public API của module.One house, walled rooms. You only pass through the door — the module's public API.
Microservices: three separate houses, each with its own database, talking over the network Orders Inventory Payments REST · gRPC · queue N deploys · N DBs
Microservices Nhiều nhà riêng, mỗi nhà điện nước riêng. Muốn nói chuyện phải gọi điện — qua mạng.Separate houses with their own utilities. To talk, you make a phone call — over the network.

Ba trục cần tách bạchThree axes to keep separate

Mọi cuộc tranh luận kiến trúc xoay quanh ba câu hỏi độc lập. Đặt mỗi kiểu lên ba trục là hết lẫn.Every architecture debate revolves around three independent questions. Place each style on the three axes and you stop confusing them.

1 · Đơn vị triển khai1 · Deployment unit

Sửa một dòng thì phải triển khai lại bao nhiêu?Change one line — how much do you redeploy?

1 khối1 unitN serviceN services

2 · Ranh giới module2 · Module boundary

Ranh giới có được công cụ ép thực thi không?Are boundaries enforced by tooling?

tự giácconventionđược épenforced

3 · Quyền sở hữu dữ liệu3 · Data ownership

Một database dùng chung, hay mỗi phần một database?One shared database, or one per part?

1 DB chung1 shared DBDB riêngDB per service

Monolith truyền thống và modular monolith trùng nhau ở trục 1 và 3; chúng chỉ khác ở trục 2. Microservices là kiểu duy nhất đẩy cả ba trục sang phải.The traditional and modular monoliths coincide on axes 1 and 3; they differ only on axis 2. Microservices is the only style that pushes all three to the right.

Chọn cái nàoWhich one to pick

Trả lời theo thứ tự, dừng ở câu "Có" đầu tiên.Answer in order; stop at the first "Yes".

Q1

Prototype/MVP, domain chưa rõ, team rất nhỏ?Prototype/MVP, unclear domain, very small team?

→ Monolith truyền thống→ Traditional monolith
Q2

Sản phẩm dài hạn, muốn code gọn và tiến hoá được?Long-term product, want clean, evolvable code?

→ Modular monolith
Q3

Có bằng chứng cụ thể cần mở rộng độc lập (team chặn nhau, một phần tải rất khác) và DevOps đã trưởng thành?Do you have concrete evidence you need independent scaling (teams blocking each other, one part with very different load) and mature DevOps?

→ Microservices, chỉ tách phần cần→ Microservices, only the parts that need it

Còn phân vân → ở lại modular monolith. Tách microservices quá sớm đắt hơn nhiều so với tách quá muộn.Still unsure → stay with the modular monolith. Splitting into microservices too early costs far more than splitting too late.

Tra cứu từng kiểuEach style in detail

Ví dụ xuyên suốt: app thương mại điện tử với Orders, Payments, Inventory.Running example: an e-commerce app with Orders, Payments, Inventory.

Monolith truyền thốngTraditional monolith

phân tầng · cổ điểnlayered · classic

Một ứng dụng build và triển khai thành một khối, code chia theo tầng kỹ thuật. Các phần gọi nhau tự do, không có rào chắn.One application built and deployed as a single block, code organized by technical layer. Parts call each other freely, with no barriers.

src/
├── controllers/
│   ├── OrderController
│   ├── PaymentController
│   └── InventoryController
├── services/
│   ├── OrderService
│   ├── PaymentService
│   └── InventoryService
├── repositories/
│   ├── OrderRepository
│   ├── PaymentRepository
│   └── InventoryRepository
└── models/

Hình dung: nhà lớn không tường ngăn. Lúc đầu tiện, nhưng sửa bếp dễ làm hỏng phòng ngủ. OrderService gọi thẳng InventoryRepository, không gì ngăn được.Analogy: a big house with no interior walls. Convenient at first, but fixing the kitchen easily breaks the bedroom. OrderService calls InventoryRepository directly and nothing stops it.

ĐượcGains

  • Khởi động cực nhanh, ít boilerplateExtremely fast to start, little boilerplate
  • Gỡ lỗi dễ: một process, một stack trace, một logEasy to debug: one process, one stack trace, one log
  • Vận hành đơn giản: một thứ để triển khai, giám sát, sao lưuSimple ops: one thing to deploy, monitor, back up
  • Gọi hàm trực tiếp, transaction ACID thoải máiDirect function calls, ACID transactions freely

MấtCosts

  • Dễ thoái hoá thành "big ball of mud"Easily degrades into a "big ball of mud"
  • Codebase phình, build/test chậm dầnCodebase grows, build/test slows down
  • Nhiều team khó làm song songHard for many teams to work in parallel
  • Coupling âm thầm tăng, refactor về sau rất đauCoupling silently grows; later refactors hurt

Đánh đổiTrade-offChọn monolith truyền thống là đổi cấu trúc dài hạn lấy tốc độ ra sản phẩm hôm nay.Choosing a traditional monolith trades long-term structure for shipping speed today.

Dùng khiUse whenPrototype, MVP, team 1–5 người, domain chưa rõPrototype, MVP, 1–5 people, unclear domain
Dấu hiệu đã hết hạnSign it's outgrownSửa Orders mà bộ test của Payments vỡChanging Orders breaks Payments tests

Modular monolith

mặc địnhthe default

Vẫn một app, một lần triển khai, một database — vận hành giống hệt monolith. Nhưng code chia theo domain nghiệp vụ, mỗi module chỉ lộ ra public interface, và ranh giới được công cụ ép.Still one app, one deploy, one database — operationally identical to a monolith. But code is split by business domain, each module exposes only a public interface, and boundaries are enforced by tooling.

src/
├── orders/
│   ├── api/       ← public interface
│   ├── internal/  ← private, hands off
│   └── data/
├── payments/
│   ├── api/
│   ├── internal/
│   └── data/
└── inventory/
    ├── api/
    ├── internal/
    └── data/

Hình dung: vẫn một ngôi nhà, một cửa chính, nhưng chia phòng bằng tường. orders cần inventory thì phải qua inventory/api, không bao giờ qua inventory/internal.Analogy: still one house with one front door, but divided into walled rooms. If orders needs inventory, it goes through inventory/api, never inventory/internal.

ĐượcGains

  • Giữ toàn bộ sự đơn giản vận hành: 1 lần triển khai, 1 DB, ACIDKeeps all monolith ops simplicity: 1 deploy, 1 DB, ACID
  • Kỷ luật kiến trúc: coupling được kiểm soátArchitectural discipline: coupling stays controlled
  • Bàn đạp rẻ sang microservices — mỗi module là một service "đang ngủ"Cheap stepping stone — each module is a "dormant" service
  • Team chia theo module, ít chồng chéoTeams split by module with little overlap

MấtCosts

  • Cần kỷ luật và công cụ, không thì thoái hoáNeeds discipline and tooling, or it decays
  • Vẫn một đơn vị triển khai: không mở rộng riêng từng phầnStill one deploy unit: can't scale parts independently
  • Phải thiết kế interface giữa module cẩn thận hơnInter-module interfaces need more careful design

⚠️ Điều kiện sống còn: ép ranh giới bằng công cụ, không bằng quy ước⚠️ Survival condition: enforce boundaries with tooling, not convention

StackCông cụTooling
Java / Kotlin (Spring)Spring Modulith, ArchUnit
.NETProject reference theo module + NetArchTestPer-module project references + NetArchTest
TypeScript / NodeESLint boundaries plugin, Nx module boundariesESLint boundaries plugin, Nx module boundaries
Pythonimport-linter
Mọi ngôn ngữAny languagePackage public/internal rõ ràng, CI báo lỗi khi vi phạm quy tắc phụ thuộcClear public/internal packages; CI fails on dependency-rule violations

Đánh đổiTrade-offChọn modular monolith là đổi một chút công thiết kế interface lấy kỷ luật kiến trúc mà không phải trả giá vận hành phân tán.Choosing a modular monolith trades some up-front interface design for architectural discipline without paying for distributed operations.

Dùng khiUse whenPhần lớn sản phẩm thực tế, dài hạnMost real-world, long-lived products
Dấu hiệu đang mụcSign it's decayingCó import vào internal/ của module khác mà CI không báoAn import into another module's internal/ passes CI

Microservices

khi thật sự cầnwhen truly needed

App tách thành nhiều service triển khai độc lập. Mỗi service build, triển khai, mở rộng riêng; sở hữu database riêng; giao tiếp với nhau qua mạng (REST, gRPC, message queue).The app is split into many independently deployed services. Each builds, deploys and scales on its own, owns its own database, and talks to others over the network (REST, gRPC, message queue).

orders-service/     ← repo + deploy + DB
payments-service/   ← repo + deploy + DB
inventory-service/  ← repo + deploy + DB

     ↕ network (REST / gRPC / queue) ↕

Hình dung: nhiều ngôi nhà riêng, mỗi nhà điện nước riêng. Nói chuyện phải gọi điện. Hoàn toàn độc lập, nhưng quản lý cả khu phố thì phức tạp.Analogy: many separate houses, each with its own utilities. To talk you make a phone call. Fully independent, but managing the whole neighborhood gets complex.

ĐượcGains

  • Nhiều team triển khai độc lập, không chặn nhauMany teams deploy independently without blocking
  • Mở rộng từng service (chỉ Orders vào ngày sale)Scale per service (only Orders on sale day)
  • Cô lập lỗi, nếu thiết kế tốtFault isolation, if designed well
  • Mỗi service chọn công nghệ riêngDifferent tech per service

MấtCosts

  • Hết ACID đơn giản → Saga, nhất quán cuối cùngNo simple ACID → Sagas, eventual consistency
  • Mạng: độ trễ, timeout, retry ở mọi lời gọiNetwork: latency, timeouts, retries on every call
  • Cần distributed tracing, log tập trung; không còn một stack traceNeeds distributed tracing, central logs; no single stack trace
  • Vận hành nặng: discovery, gateway, CI/CD × N, KubernetesHeavy ops: discovery, gateway, CI/CD × N, Kubernetes
  • Dữ liệu nhân bản, phải đồng bộ bằng eventDuplicated data, synced via events

Đánh đổiTrade-offChọn microservices là đổi sự đơn giản khi vận hành lấy khả năng mở rộng theo tổ chức và theo tài nguyên.Choosing microservices trades operational simplicity for organizational and resource scalability.

Dùng khiUse whenNhiều team cần triển khai độc lập, một phần tải rất khác, DevOps trưởng thànhMany teams need independent deploys, one part has very different load, mature DevOps
Dấu hiệu tách saiSign of a bad splitMột tính năng phải triển khai 3 service cùng lúcOne feature needs 3 services deployed together

Bảng so sánh chi tiếtDetailed comparison

Tiêu chíCriterionTruyền thốngTraditionalModularMicroservices
Đơn vị triển khaiDeployment unit11N
Cách chia codeHow code is splitTầng kỹ thuậtTechnical layerDomain nghiệp vụBusiness domainDomain nghiệp vụBusiness domain
Ranh giới moduleModule boundariesYếu, không épWeak, not enforcedChặt, ép trong codeStrong, enforced in codeChặt, qua mạngStrong, over the network
Database1 chung1 shared1 chung (tách schema được)1 shared (schemas can split)Mỗi service một1 per service
Giao tiếp nội bộInternal communicationGọi hàm trực tiếpDirect function callsQua public interfaceVia public interfaceMạng (REST/gRPC/queue)Network (REST/gRPC/queue)
TransactionACID dễEasy ACIDACID dễEasy ACIDSaga / nhất quán cuối cùngSaga / eventual consistency
Độ phức tạp vận hànhOperational complexityThấpLowThấpLowCaoHigh
Tốc độ build/testBuild/test speedNhanh → chậm dầnFast → slows over timeNhanh, kiểm thử được từng moduleFast, testable per moduleTừng service nhanh, tổng thể phức tạpEach fast, overall complex
Mở rộng theo teamIndependent team scalingKhóHardTrung bìnhMediumDễEasy
Mở rộng tài nguyênResource scalingCả app cùng nhauWhole app togetherCả app cùng nhauWhole app togetherTheo từng servicePer service
Cô lập lỗiFault isolationKémPoorKém (cùng process)Poor (same process)Tốt (nếu thiết kế đúng)Good (if designed right)
Chi phí chuyển sang microservicesCost to move to microservicesCao (phải gỡ rối trước)High (untangle first)Thấp (module sẵn sàng tách)Low (modules ready to split)—
Yêu cầu hạ tầngInfrastructure needsTối thiểuMinimalTối thiểuMinimalCao (container, orchestration, gateway…)High (containers, orchestration, gateway…)

Hiểu lầm thường gặpCommon misconceptions

Hiểu lầmMisconceptionVì sao saiWhy it's wrongNghĩ đúngThink instead
"Microservices hiện đại hơn monolith""Microservices are more modern than monoliths"Không nhanh hơn, không "đúng hơn" — nó đánh đổi sự đơn giản lấy khả năng mở rộngNot faster, not "more correct" — it trades simplicity for scalabilityChỉ trả cái giá đó khi cần đúng thứ nó muaPay that price only when you need what it buys
"Modular và microservices chỉ khác chỗ triển khai""Modular vs microservices differs only in deployment"Khác biệt sâu hơn là quyền sở hữu dữ liệu và ranh giới mạngThe deeper difference is data ownership and the network boundaryTách DB và đi qua mạng mới là nguồn độ phức tạpSplit DBs and network calls are the real source of complexity
"Chia thư mục theo domain là đủ modular""Folders by domain make it modular"Không ép ranh giới thì code dần đi đường tắtWithout enforcement, code takes shortcutsCI phải báo lỗi khi một module chọc vào internal/ của module khácCI must fail when a module reaches into another's internal/
"Làm microservices luôn từ đầu để khỏi chuyển đổi""Start with microservices to avoid migrating later"Domain chưa rõ thì vẽ sai ranh giới → distributed monolith: đủ độ phức tạp phân tán, không được độc lậpUnclear domain → wrong boundaries → a distributed monolith: all the complexity, none of the independenceBắt đầu modular, tách khi có lý do đo đượcStart modular; split for a measured reason

Lộ trình tiến hoáEvolution path

Mỗi module đã có ranh giới rõ là một microservice "đang ngủ" — tách nó ra rẻ hơn rất nhiều so với gỡ một monolith rối.A module with clear boundaries is a "dormant" microservice — splitting it out is far cheaper than untangling a messy monolith.

Monolith truyền thốngTraditional monolithRa sản phẩm nhanhShip fast
chia theo domain, ép ranh giớisplit by domain, enforce boundaries
Modular monolithPhần lớn dự án nên dừng ở đâyMost projects should stay here
tách một module vì lý do cụ thểsplit one module for a concrete reason
MicroservicesMột phần hoặc toàn bộPartial or full

Ba câu cần nhớThree things to remember

Modular monolith trước, microservices khi thấy đau.Modular monolith first, microservices when it hurts.