Đội ngũ năm người, sản phẩm chưa có khách hàng trả tiền, nhưng bản thiết kế kiến trúc đã có mười hai microservice, một service mesh và ba loại database khác nhau. Nghe có vẻ phóng đại, nhưng đây là kịch bản không hiếm gặp ở các startup công nghệ. Microservices gắn liền với những cái tên lớn như Netflix hay Amazon, khiến nhiều người nghĩ đó là con đường "chuẩn" để xây dựng phần mềm hiện đại. Thực tế phức tạp hơn nhiều. Bài viết này giúp bạn đánh giá hai lựa chọn một cách tỉnh táo, dựa trên bối cảnh thực của một startup.

Monolith là gì?

Monolith là ứng dụng được xây dựng và triển khai như một khối thống nhất. Toàn bộ chức năng, từ xác thực, quản lý sản phẩm, thanh toán đến thông báo, nằm trong cùng một codebase, chạy trong cùng một tiến trình và thường dùng chung một cơ sở dữ liệu.

Ưu điểm nổi bật:

  • Đơn giản khi phát triển và debug: gọi hàm thay vì gọi mạng, stack trace liền mạch từ đầu đến cuối.
  • Triển khai dễ dàng: một artifact, một pipeline.
  • Transaction đơn giản: cập nhật nhiều bảng trong một transaction cơ sở dữ liệu là xong.
  • Chi phí vận hành thấp: ít thành phần hạ tầng cần giám sát.

Nhược điểm xuất hiện khi hệ thống và đội ngũ lớn dần: codebase dễ trở nên rối nếu thiếu kỷ luật, mỗi lần triển khai phải đưa cả ứng dụng lên, khó mở rộng riêng từng phần, và nhiều đội cùng sửa một codebase dễ giẫm chân nhau.

Microservices là gì?

Microservices chia hệ thống thành nhiều dịch vụ nhỏ, mỗi dịch vụ phụ trách một năng lực nghiệp vụ, có thể triển khai độc lập, thường sở hữu dữ liệu riêng và giao tiếp qua mạng bằng HTTP, gRPC hay hàng đợi tin nhắn.

Ưu điểm:

  • Triển khai độc lập: đội thanh toán phát hành mà không cần chờ đội tìm kiếm.
  • Mở rộng có chọn lọc: chỉ tăng tài nguyên cho dịch vụ chịu tải cao.
  • Tự chủ cho từng đội: mỗi đội sở hữu trọn vẹn một dịch vụ.
  • Cô lập lỗi tốt hơn nếu được thiết kế cẩn thận.

Nhưng cái giá phải trả không nhỏ. Martin Fowler gọi đó là microservice premium: chi phí phức tạp mà bạn phải trả ngay từ đầu, bao gồm:

  • Giao tiếp qua mạng có độ trễ và có thể thất bại; cần timeout, retry, circuit breaker.
  • Dữ liệu phân tán, khó đảm bảo nhất quán; phải làm quen với nhất quán cuối cùng (eventual consistency) và các mẫu như Saga.
  • Cần hạ tầng trưởng thành: CI/CD cho nhiều dịch vụ, service discovery, logging tập trung, distributed tracing.
  • Debug một lỗi có thể đòi hỏi lần theo request qua nhiều dịch vụ.

Bối cảnh đặc thù của startup

Startup giai đoạn đầu có vài đặc điểm quan trọng:

  1. Chưa hiểu rõ nghiệp vụ: sản phẩm thay đổi liên tục để tìm product-market fit. Ranh giới giữa các phần còn mờ.
  2. Đội ngũ nhỏ: vài kỹ sư không thể vận hành hàng chục dịch vụ mà vẫn giữ tốc độ ra tính năng.
  3. Tốc độ là sống còn: thời gian dành cho hạ tầng là thời gian không dành cho khách hàng.
  4. Tải chưa lớn: phần lớn startup chưa gặp vấn đề mở rộng mà microservices giải quyết.

Vấn đề lớn nhất là: nếu vẽ ranh giới dịch vụ sai khi chưa hiểu nghiệp vụ, việc sửa lại trong microservices tốn kém hơn nhiều so với refactor trong một codebase. Đây là lý do nhiều chuyên gia ủng hộ cách tiếp cận "Monolith First": bắt đầu bằng monolith, chỉ tách khi đã hiểu rõ hệ thống.

Lựa chọn trung dung: Modular Monolith

Monolith không đồng nghĩa với "mớ code hỗn độn". Modular monolith là ứng dụng triển khai như một khối, nhưng bên trong được chia thành các mô-đun có ranh giới rõ ràng:

  • Mỗi mô-đun tương ứng một miền nghiệp vụ (đơn hàng, thanh toán, người dùng...).
  • Mô-đun chỉ giao tiếp qua giao diện công khai, không truy cập trực tiếp vào bảng hay lớp nội bộ của nhau.
  • Có thể dùng công cụ kiểm tra phụ thuộc trong CI để ngăn vi phạm ranh giới.

Cách này giữ được sự đơn giản khi vận hành, đồng thời chuẩn bị sẵn đường lui: khi một mô-đun thực sự cần tách ra, ranh giới đã rõ ràng nên việc tách trở nên khả thi hơn nhiều.

Khi nào nên tách thành microservices?

Hãy cân nhắc tách một phần ra thành dịch vụ riêng khi có tín hiệu cụ thể, chẳng hạn:

  • Một chức năng có yêu cầu tải hoặc tài nguyên khác biệt hẳn, ví dụ xử lý video, chạy mô hình học máy.
  • Nhiều đội cùng làm việc trên một codebase và thường xuyên chặn nhau khi phát hành.
  • Một phần cần chu kỳ phát hành, công nghệ hoặc yêu cầu bảo mật riêng biệt.
  • Ranh giới nghiệp vụ đã ổn định qua nhiều chu kỳ phát triển.

Khi tách, hãy làm từng bước. Mẫu Strangler Fig cho phép bạn dần dần chuyển chức năng từ hệ thống cũ sang dịch vụ mới, thay vì viết lại toàn bộ trong một lần.

Những câu hỏi nên tự hỏi

  1. Đội ngũ đã có CI/CD, giám sát và logging tập trung đủ tốt chưa?
  2. Vấn đề hiện tại thực sự do kiến trúc, hay do chất lượng code và quy trình?
  3. Có thể giải quyết bằng cách tách mô-đun rõ ràng hơn, thêm cache hay mở rộng theo chiều ngang monolith không?
  4. Ai sẽ trực và xử lý sự cố khi có mười dịch vụ thay vì một?

Kết luận

Với phần lớn startup, một monolith được mô-đun hoá tốt là lựa chọn khôn ngoan: phát triển nhanh, vận hành đơn giản, chi phí thấp và vẫn mở đường cho việc tách dịch vụ sau này. Microservices là công cụ mạnh để giải quyết bài toán quy mô tổ chức và quy mô hệ thống, nhưng chỉ nên áp dụng khi bạn thực sự gặp những bài toán đó. Kiến trúc tốt nhất là kiến trúc phù hợp với giai đoạn hiện tại của sản phẩm và đội ngũ.

Nguồn tham khảo