Thiết kế hệ thống1 tháng 6, 20265 phút đọc

5 Năm Trước: Sự Ngỡ Ngàng Của Một Mid-Level Khi Nhận "Cục Tạ" Monorepo... Chạy Bằng Bazel

5 Năm Trước: Sự Ngỡ Ngàng Của Một Mid-Level Khi Nhận "Cục Tạ" Monorepo... Chạy Bằng Bazel

Năm năm trước, khi tôi còn là một Mid-level đang quen với những dự án Simple Repo (Polyrepo) cứ npm run dev hay go run main.go là chạy, thì team tôi nhận bàn giao một cụm dự án khổng lồ.

Anh Tech Lead team bạn gửi repo kèm một câu dặn dò mà lúc đó tôi chưa hiểu hết độ "nặng đô" của nó: "Code chung một repo hết nhé, bọn anh dùng Bazel để build".

Tôi hí hửng git clone về, hí hửng mở code lên và... hoàn toàn đứng hình. Không có những file script quen thuộc. Đập vào mắt tôi là hàng chục file mang tên WORKSPACE và BUILD.bazel rải rác khắp các thư mục. Đó là khởi đầu cho chuỗi ngày "vỡ mật" nhưng đáng giá nhất trong sự nghiệp của tôi.

1. Cú Sốc Mang Tên "Cái Gì Cũng Phải Khai Báo" (Explicit Dependencies)

Với một Mid-level thời đó, tư duy của tôi là: muốn dùng thư viện gì thì cứ import, thiếu thì cài thêm vào package.json hoặc go.mod. Nhưng bước vào thế giới của Bazel, quy luật đó hoàn toàn bị phá vỡ.

Bazel hoạt động theo triết lý: Hermetic Computing (Tính toán khép kín). Nghĩa là nó không tin tưởng bất cứ thứ gì có sẵn trên máy của bạn.

  • Một file code ở service-a muốn gọi một hàm helper ở shared-lib? Không đơn giản là gõ đường dẫn tương đối. Tôi phải vào file BUILD.bazel của service-a, khai báo tường minh rằng: "Tôi chuẩn bị phụ thuộc vào cái target //packages/shared-lib này đây".

  • Quên khai báo? Bazel sẽ lập tức báo lỗi ngay khi build, dù cho IDE của bạn có nhận được code đi chăng nữa.

Tuần đầu tiên tiếp quản, tôi dành một nửa thời gian chỉ để... học cú pháp Starlark (một dạng của Python) để viết các file BUILD. Cảm giác lúc đó vừa ức chế, vừa tự hỏi: "Tại sao người ta lại tự làm khó mình thế này?"

2. Và Rồi... "Phép Màu" Xuất Hiện Khi Hệ Thống Phình To

Sau khi vượt qua được rào cản cấu hình, bắt đầu vào guồng fix bug và tính năng mới, tôi mới nhận ra Bazel không phải là một công cụ "hành xác", mà nó là một kiệt tác kỹ thuật dành cho Monorepo quy mô lớn.

Hệ thống bàn giao về có tới gần chục service lớn nhỏ. Nếu chạy CI/CD thông thường cho một Monorepo, mỗi lần tôi push code, hệ thống sẽ mất cả tiếng đồng hồ để test và build lại mọi thứ. Nhưng với Bazel, câu chuyện hoàn toàn khác:

"Affected Build" và "Caching" ở đẳng cấp tối thượng

Bazel hiểu rõ sơ đồ phụ thuộc (Dependency Graph) của toàn bộ repo như lòng bàn tay. Khi tôi sửa một dòng code trong service-frontend-a:

  • Bazel thừa biết thay đổi này chẳng liên quan gì đến service-backend-b hay service-mobile.

  • Khi chạy lệnh build, nó chỉ build đúng service-frontend-a.

  • Còn đống service còn lại? Bazel lôi từ trong Cache ra (thậm chí là Remote Cache chia sẻ giữa máy tôi và Server CI/CD) với tốc độ tính bằng mili-giây.

Slogan của Bazel: "Correct, Choose two: Fast or Reproducible." (Với Bazel, bạn có cả hai. Build một lần là chuẩn cả đời, không bao giờ có chuyện "Code chạy trên máy em nhưng lỗi trên server").

Lần đầu tiên trải nghiệm Monorepo đa ngôn ngữ (Polyglot)

Dự án bàn giao về không chỉ dùng một ngôn ngữ. Nó có Frontend bằng TypeScript, một service core bằng Go, và vài script automation bằng Python. Nếu không có Bazel, team tôi sẽ phải cài đủ thứ runtime (Node, Go, Python) trên máy local và cấu hình quyền chạy rất phức tạp. Với Bazel, nó tự động tải đúng phiên bản Go, đúng phiên bản Node biệt lập về một thư mục ẩn và tự chạy. Tôi chỉ cần gõ đúng một lệnh duy nhất: bazel test //... là toàn bộ test case của cả ba ngôn ngữ cùng chạy song song, mượt mà.

Lời Kết

Năm năm trước, việc tiếp quản một Monorepo chạy Bazel giống như việc một tài xế đang quen lái xe số sàn phân khối nhỏ bỗng nhiên bị đẩy lên buồng lái của một chiếc Boeing 747. Cực kỳ ngợp, bảng điều khiển dày đặc nút bấm, và chỉ một sai sót nhỏ trong file cấu hình cũng khiến "máy bay" không thể cất cánh.

Nhưng chính cú va chạm nảy lửa với Bazel năm đó đã rèn cho một Mid-level như tôi một tư duy cực kỳ khắt khe về Dependency Management (Quản lý phụ thuộc) và Build System. Nó buộc tôi phải hiểu sâu xuống tận tầng kiến trúc: Code của mình thực sự được biên dịch như thế nào, hệ thống CI/CD vận hành ra sao. Đó là bước đệm vô giá để tôi không còn sợ bất kỳ hệ thống quy mô lớn (Enterprise) nào sau này.

Bài viết liên quan

Trong thời đại AI, kiến thức thật sự đến từ đâu?
Trí tuệ nhân tạo11 min

Trong thời đại AI, kiến thức thật sự đến từ đâu?

AI khuếch đại kiến thức của người đã có kiến thức, và khuếch đại luôn cả sự nhầm lẫn của người chưa có. Bài viết lập bản đồ 6 nguồn kiến thức — từ sách, tài liệu gốc đến thực hành — để bạn giữ quyền phán xét cuối cùng, thay vì phó mặc cho bộ lọc vô hình của mô hình ngôn ngữ.

11 thg 8, 2026Đọc
Sáu trạm, sáu cách chết: hành trình sống còn của một HTTP request
Thiết kế hệ thống19 min

Sáu trạm, sáu cách chết: hành trình sống còn của một HTTP request

Ghi chép kỹ thuật & chiêm nghiệm — về con đường mà mỗi request phải đi, và tại sao nó lại đi như vậy.

14 thg 6, 2026Đọc
Tôi viết một con DNS server để hiểu thứ mình đã tin tưởng mù quáng suốt mười năm
Thiết kế hệ thống16 min

Tôi viết một con DNS server để hiểu thứ mình đã tin tưởng mù quáng suốt mười năm

Có những công nghệ bạn dùng mỗi ngày mà chưa từng nhìn thẳng vào mặt nó. Với tôi, DNS là một trong số đó. Tôi gõ tên miền cả thập kỷ. Mua domain, trỏ record, chờ "propagate", chửi thề khi nó không lên, rồi vui khi nó lên — mà thật ra không hiểu tại sao nó lên. DNS với tôi giống công tắc điện trong nhà: bật là sáng, hỏng thì gọi thợ. Cho đến một đêm deploy, web chết. ping ra IP vẫn ngon, curl thẳng vào IP vẫn trả về trang. Chỉ riêng cái tên miền là im lặng. Tôi ngồi đó và nhận ra một điều hơi nhục: tôi không biết bắt đầu sửa từ đâu, vì tôi chưa bao giờ thật sự hiểu cái thứ đang hỏng. Nên tôi làm điều mà chắc chỉ dân kỹ thuật mới thấy hợp lý: thay vì đọc tài liệu cho qua, tôi ngồi viết hẳn một con DNS server bằng Rust. Project tên mini-dns. Bài này là những gì một hộp đen kể cho tôi nghe khi tôi cuối cùng cũng chịu mở nó ra.

13 thg 6, 2026Đọc