Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là Technical BA trong một dự án xây dựng hệ thống thương mại điện tử mới. Trong buổi họp kiến trúc đầu tiên, Solution Architect hỏi: "Mình nên đi monolith hay microservices?". Tất cả mọi người nhìn về phía bạn — người được kỳ vọng nối cây cầu giữa nghiệp vụ và kỹ thuật. Nếu bạn trả lời "microservices cho nó hiện đại" mà không hiểu cái giá phải trả, bạn vừa đẩy dự án vào một con đường có thể đắt gấp ba lần, chậm gấp đôi và đầy rủi ro vận hành.
Là Technical BA, bạn không cần code ra hệ thống, nhưng bạn cần hiểu đủ sâu để biết quyết định kiến trúc ảnh hưởng thế nào đến yêu cầu nghiệp vụ, đến cách bạn viết spec, đến timeline, đến chi phí, và đến trải nghiệm người dùng. Quyết định monolith hay microservices không phải là quyết định "công nghệ thuần túy" — nó là quyết định về tổ chức, về tốc độ ra thị trường, về khả năng mở rộng theo team và theo tải.
Bài này trang bị cho bạn khung tư duy để: (1) phân biệt rạch ròi hai kiến trúc, (2) đọc hiểu khi engineer tranh luận, và (3) đóng góp tiếng nói có trọng lượng vào quyết định kiến trúc — thay vì chỉ gật gù.
Khái niệm cốt lõi
Monolith là gì?
Monolith (kiến trúc khối) là cách xây dựng ứng dụng trong đó toàn bộ chức năng nằm trong một codebase duy nhất, đóng gói và triển khai (deploy) thành một đơn vị duy nhất. Module quản lý người dùng, module giỏ hàng, module thanh toán, module kho — tất cả sống chung trong một ứng dụng, gọi nhau bằng lệnh gọi hàm nội bộ (in-process call), và dùng chung một cơ sở dữ liệu.
Ưu điểm cốt lõi của monolith:
- Đơn giản để bắt đầu và vận hành. Một repo, một pipeline build, một lần deploy. Đội nhỏ không tốn công thiết lập hạ tầng phức tạp.
- Dễ debug và truy vết. Khi có lỗi, bạn lần theo stack trace trong cùng một tiến trình. Không phải nhảy qua nhảy lại giữa 15 dịch vụ.
- Giao dịch (transaction) dễ đảm bảo. Vì dùng chung một database, bạn có thể bọc nhiều thao tác (trừ tiền tài khoản, ghi đơn hàng, trừ kho) trong một transaction ACID duy nhất. Hoặc tất cả thành công, hoặc tất cả rollback. Đây là điểm cực mạnh với nghiệp vụ tài chính, ngân hàng.
- Hiệu năng gọi nội bộ cao. Gọi hàm trong cùng tiến trình mất vài nano giây; gọi qua mạng giữa các service mất vài mili giây — chậm hơn cả nghìn lần.
- Khó scale theo team. Khi 60 lập trình viên cùng đụng vào một codebase, xung đột merge (merge conflict) xảy ra liên tục, mỗi lần deploy phải chờ tất cả mọi người sẵn sàng.
- Scale tài nguyên kiểu "tất cả hoặc không gì". Nếu chỉ module tìm kiếm bị quá tải, bạn vẫn buộc phải nhân bản toàn bộ ứng dụng (cả module thanh toán, kho...) để chịu tải — lãng phí.
- "Khóa cứng" công nghệ. Cả khối phải dùng chung một ngôn ngữ, một framework. Muốn viết module xử lý ảnh bằng Python trong khi hệ thống là Java? Khó.
- Một lỗi có thể kéo sập cả hệ thống. Một memory leak ở module báo cáo có thể làm chết luôn cả tiến trình đang xử lý thanh toán.
Microservices là gì?
Microservices (kiến trúc vi dịch vụ) chia hệ thống thành nhiều dịch vụ nhỏ, độc lập, mỗi dịch vụ phụ trách một mảng nghiệp vụ và được triển khai riêng. Service Đơn hàng, service Thanh toán, service Kho, service Người dùng — mỗi cái có codebase riêng, database riêng (hoặc schema riêng), pipeline deploy riêng, và giao tiếp với nhau qua mạng (thường là REST API, gRPC, hoặc message queue).
Ưu điểm cốt lõi:
- Scale độc lập. Module tìm kiếm quá tải? Nhân bản riêng service tìm kiếm lên 10 instance, các service khác giữ nguyên. Tiết kiệm tài nguyên đáng kể.
- Triển khai độc lập (independent deployment). Team Thanh toán có thể deploy bản vá lúc 2 giờ chiều mà không cần đợi team Kho. Tốc độ ra mắt tính năng nhanh hơn rõ rệt khi có nhiều team.
- Cô lập lỗi (fault isolation). Service Báo cáo sập không nhất thiết làm sập service Thanh toán — nếu được thiết kế đúng với cơ chế chịu lỗi.
- Tự do công nghệ. Mỗi team chọn ngôn ngữ, database phù hợp nhất cho bài toán của mình.
- Phức tạp vận hành tăng vọt. Bạn không còn quản 1 ứng dụng, mà quản 30 ứng dụng, 30 pipeline, hàng trăm kết nối mạng. Cần đầu tư observability (giám sát), service discovery, API gateway.
- Giao dịch phân tán (distributed transaction) rất khó. Vì mỗi service có database riêng, bạn không thể bọc "trừ tiền + ghi đơn + trừ kho" trong một transaction ACID nữa. Phải dùng các pattern phức tạp như Saga, eventual consistency (nhất quán cuối cùng) — nghĩa là dữ liệu có thể "tạm thời không khớp" trong vài giây.
- Độ trễ mạng và lỗi mạng. Mỗi lời gọi giữa service là một lời gọi qua mạng — có thể chậm, có thể timeout, có thể thất bại từng phần.
- Khó debug end-to-end. Một request đi qua 8 service; muốn tìm chỗ chậm phải có distributed tracing.
Bảng so sánh nhanh để bạn ghi nhớ
| Tiêu chí | Monolith | Microservices |
|---|---|---|
| Codebase / deploy | Một khối | Nhiều dịch vụ độc lập |
| Database | Dùng chung | Riêng từng service |
| Transaction | ACID dễ dàng | Saga / eventual consistency |
| Scale | Toàn bộ ứng dụng | Từng service riêng |
| Phù hợp team | Nhỏ (dưới ~15 người) | Nhiều team, quy mô lớn |
| Chi phí vận hành ban đầu | Thấp | Cao |
| Độ trễ gọi nội bộ | Rất thấp | Cao hơn (qua mạng) |
Một lưu ý quan trọng: đây không phải lựa chọn nhị phân
Nhiều người nghĩ phải chọn "100% monolith" hoặc "100% microservices". Thực tế, có một bước trung gian rất giá trị: modular monolith — vẫn deploy một khối, nhưng bên trong được chia module rõ ràng, ranh giới sạch sẽ, để sau này tách ra microservices dễ dàng khi thật sự cần. Đây thường là lựa chọn khôn ngoan nhất cho startup và dự án vừa.
Tình huống thực tế
Tình huống 1 — Startup giao đồ ăn "FoodNow" chọn microservices quá sớm
FoodNow là startup giao đồ ăn ở TP.HCM, gọi vốn vòng seed 800.000 USD, đội ngũ kỹ thuật chỉ 6 người. CTO mới về từ một tập đoàn lớn, quyết tâm "làm chuẩn ngay từ đầu" và xây 14 microservices: Auth, User, Restaurant, Menu, Cart, Order, Payment, Delivery, Notification, Rating, Promotion, Search, Analytics, Admin.
Sau 8 tháng, hệ thống mới chỉ có khoảng 3.000 đơn/ngày — tải mà một monolith chạy trên một server vừa phải cũng dư sức gánh. Nhưng đội 6 người phải gồng mình duy trì 14 pipeline CI/CD, 14 bộ log, một cụm Kubernetes mà không ai thật sự thành thạo. Mỗi tính năng mới (ví dụ "đặt đơn nhóm") phải sửa 4–5 service cùng lúc, phối hợp deploy theo thứ tự. Một sự cố "đơn hàng đã trừ tiền nhưng không ghi nhận" xảy ra vì giao dịch phân tán giữa Payment và Order không nhất quán — phải xử lý hoàn tiền thủ công cho hơn 200 khách.
Bài học rút ra: Microservices giải quyết bài toán quy mô tổ chức và tải lớn, không phải bài toán "trông cho hiện đại". Với 6 người và 3.000 đơn/ngày, một modular monolith sẽ giúp FoodNow đi nhanh hơn nhiều, ít sự cố hơn, và vẫn để ngỏ đường tách service về sau. Là BA, khi bạn thấy đội nhỏ mà spec đòi hỏi hàng chục service, hãy đặt câu hỏi: "Tải dự kiến bao nhiêu? Bao nhiêu team sẽ làm song song?".
Tình huống 2 — Sàn TMĐT "Tiki-style" tách monolith đúng chỗ đau
Một sàn thương mại điện tử lớn của Việt Nam (gọi là sàn X) khởi đầu bằng monolith — và đó là quyết định đúng cho giai đoạn đầu. Nhưng sau 4 năm, vào mỗi đợt sale lớn (như 11/11), module Tìm kiếm sản phẩm và module Giỏ hàng bị tải cực nặng, trong khi module Quản lý nhà bán gần như nhàn rỗi. Vì là monolith, để chịu tải đỉnh, họ phải nhân bản toàn bộ ứng dụng lên hàng chục instance — tốn kém và lãng phí RAM cho những phần không cần.
Thay vì đập đi xây lại (rủi ro khổng lồ), họ áp dụng pattern Strangler Fig: tách dần từng phần nóng nhất ra microservice riêng. Đầu tiên là service Tìm kiếm (chuyển sang Elasticsearch, scale riêng), rồi service Giỏ hàng, rồi service Thanh toán. Phần còn lại vẫn nằm trong monolith. Sau 18 tháng, hệ thống cõng được tải sale gấp 5 lần với chi phí hạ tầng giờ cao điểm giảm rõ rệt vì chỉ scale đúng phần cần.
Bài học rút ra: Tách microservices nên theo điểm đau cụ thể (tải, tốc độ deploy, ranh giới đội), không tách đồng loạt. Là BA, bạn là người nắm dữ liệu nghiệp vụ — bạn biết "module nào bị tải khi nào, đơn hàng tăng vào dịp nào" — và chính dữ liệu đó giúp quyết định tách cái gì trước.
Tình huống 3 — Ngân hàng số và bài toán transaction
Một ngân hàng số tại Đông Nam Á xây dựng lõi xử lý chuyển khoản. Đội kỹ thuật ban đầu định tách "Tài khoản" và "Sổ cái (ledger)" thành hai microservice riêng cho "hiện đại". Nhưng Technical BA phụ trách đã chỉ ra: một giao dịch chuyển tiền bắt buộc phải đảm bảo trừ tiền tài khoản A và cộng cho tài khoản B xảy ra trọn vẹn — không được phép có trạng thái "đã trừ A nhưng chưa cộng B". Nếu tách hai service hai database, họ phải triển khai Saga với cơ chế bù trừ (compensating transaction) phức tạp, tăng rủi ro sai lệch tiền — điều không thể chấp nhận trong ngân hàng.
Kết quả: nhóm giữ phần lõi sổ cái trong một service với một database giao dịch ACID mạnh, chỉ tách những phần ngoại vi (thông báo, lịch sử, báo cáo) ra microservice. Đây là kiến trúc lai (hybrid) hợp lý.
Bài học rút ra: Yêu cầu nhất quán mạnh (strong consistency) của nghiệp vụ là một ràng buộc kiến trúc bậc nhất. Nơi nào tiền bạc và pháp lý đòi hỏi "tất cả hoặc không gì", monolith/một-database thường an toàn hơn microservices. BA chính là người phát hiện ràng buộc này từ nghiệp vụ.
Hướng dẫn từng bước
Khi tham gia một quyết định kiến trúc với vai trò Technical BA, hãy đi theo trình tự sau:
Bước 1 — Làm rõ quy mô tổ chức. Hỏi: Hiện có bao nhiêu lập trình viên? Dự kiến tăng lên bao nhiêu team trong 12–18 tháng? Microservices chỉ thật sự "đáng tiền" khi có nhiều team cần deploy độc lập. Một team nhỏ thì monolith gần như luôn thắng.
Bước 2 — Lượng hóa tải và mẫu tải. Thu thập con số: số giao dịch/giây hiện tại và đỉnh, mẫu tăng trưởng (đều hay theo mùa/sự kiện), phần nào tải nặng hơn phần nào. Nếu tải đồng đều và vừa phải, monolith ổn. Nếu có "điểm nóng" lệch hẳn, đó là tín hiệu cho việc tách service.
Bước 3 — Xác định ràng buộc nhất quán dữ liệu. Với từng luồng nghiệp vụ quan trọng, hỏi: Nghiệp vụ có chấp nhận "nhất quán cuối cùng" (chênh lệch vài giây) không, hay bắt buộc nhất quán tức thì? Luồng tiền bạc, kho hàng giới hạn, đặt vé thường cần nhất quán mạnh — nghiêng về monolith/một-database.
Bước 4 — Đánh giá năng lực vận hành. Hỏi đội DevOps/hạ tầng: Mình đã có observability, CI/CD trưởng thành, có người thạo Kubernetes chưa? Microservices không có nền tảng vận hành tốt là tự rước nợ.
Bước 5 — Lập bản đồ bounded context (ranh giới nghiệp vụ). Phác họa các mảng nghiệp vụ và ranh giới giữa chúng. Ranh giới nghiệp vụ rõ ràng là cơ sở để sau này tách service. (Phần này được đào sâu ở bài Domain-Driven Design.)
Bước 6 — Đề xuất theo nguyên tắc "monolith trước, tách khi cần". Trừ khi có lý do tổ chức/tải rõ rệt, hãy đề xuất bắt đầu bằng modular monolith với ranh giới sạch, và lên kế hoạch tách dần (Strangler Fig) theo điểm đau thực tế.
Bước 7 — Ghi lại quyết định. Viết một bản tóm tắt: bối cảnh, các phương án, lý do chọn, đánh đổi chấp nhận. Đây là tiền đề của ADR (Architecture Decision Record) sẽ học ở bài sau.
Lỗi thường gặp & mẹo
Lỗi 1 — Chọn microservices vì "thời thượng". Đây là sai lầm đắt nhất. Microservices là công cụ giải bài toán quy mô, không phải huy hiệu hiện đại. Mẹo: luôn hỏi "bài toán cụ thể nào của chúng ta khiến monolith không đáp ứng được?". Nếu không trả lời được, đừng tách.
Lỗi 2 — Quên mất giao dịch phân tán. BA viết spec "đặt đơn thì trừ kho và trừ tiền cùng lúc", rồi engineer mới phát hiện hai thao tác này nằm ở hai service hai database — không thể đảm bảo nguyên tử. Mẹo: với mỗi luồng đụng nhiều dữ liệu, hãy hỏi rõ yêu cầu nhất quán ngay từ giai đoạn spec.
Lỗi 3 — Chia service quá nhỏ (nano-services). Mỗi service chỉ làm một việc tí xíu, dẫn đến một nghiệp vụ phải gọi qua chục service, độ trễ và lỗi mạng tăng vọt. Mẹo: ranh giới service nên theo mảng nghiệp vụ có ý nghĩa, không theo từng bảng dữ liệu.
Lỗi 4 — Nhiều service nhưng dùng chung một database. Đây là "monolith trá hình" — có đủ cái khó của cả hai mà không được lợi ích nào. Mẹo: nếu đã tách service, mỗi service phải sở hữu dữ liệu riêng; nếu chưa làm được điều đó thì chưa nên tách.
Mẹo vàng cho BA: Trong mọi cuộc tranh luận kiến trúc, hãy mang dữ liệu nghiệp vụ đến bàn — số giao dịch, mẫu tải theo mùa, ràng buộc pháp lý, số team. Engineer giỏi về kỹ thuật, nhưng bạn mới là người nắm bức tranh nghiệp vụ làm cơ sở cho quyết định đúng.
Bài tập thực hành
Bài 1 — Phân loại tình huống. Với mỗi tình huống dưới đây, hãy quyết định nên monolith hay microservices (hoặc lai) và viết 2–3 câu lý do:
- (a) Một MVP đặt lịch spa, đội 3 lập trình viên, dự kiến 500 lượt đặt/ngày.
- (b) Một siêu ứng dụng (super-app) với 8 team, hàng triệu người dùng, các mảng ví điện tử, gọi xe, giao đồ ăn phát triển song song.
- (c) Hệ thống lõi xử lý hạn mức tín dụng cần nhất quán tuyệt đối.
Bài 3 — Viết câu hỏi điều tra. Soạn 5 câu hỏi bạn sẽ đặt cho Solution Architect và team Business trong buổi họp quyết định kiến trúc, bám theo 7 bước trong phần Hướng dẫn ở trên.
Bài 4 — Phát hiện rủi ro transaction. Cho luồng: "Khách đặt vé concert: hệ thống giữ ghế, trừ tiền, gửi vé qua email". Nếu mỗi bước nằm ở một microservice khác nhau, hãy chỉ ra rủi ro có thể xảy ra và đề xuất cách xử lý ở mức ý tưởng.
Tóm tắt
- Monolith là một codebase, một deploy, một database: đơn giản, dễ debug, transaction ACID dễ dàng, nhưng khó scale theo team và theo điểm nóng tải.
- Microservices chia hệ thống thành nhiều dịch vụ độc lập, mỗi cái database riêng: scale và deploy độc lập, cô lập lỗi, nhưng phức tạp vận hành cao, giao dịch phân tán khó, và đòi hỏi nền tảng observability/CI-CD trưởng thành.
- Đây không phải lựa chọn nhị phân — modular monolith và kiến trúc lai thường là lựa chọn khôn ngoan. Nguyên tắc thực dụng: "monolith trước, tách dần khi có điểm đau thực sự" (Strangler Fig).
- Ba ràng buộc quyết định lớn nhất: quy mô tổ chức (số team), mẫu tải (điểm nóng), và yêu cầu nhất quán dữ liệu (tiền bạc/pháp lý cần nhất quán mạnh).
- Vai trò của Technical BA: mang dữ liệu nghiệp vụ vào bàn quyết định, phát hiện ràng buộc transaction từ giai đoạn spec, và ghi lại quyết định kiến trúc một cách có cơ sở.