Microservices và Modular Monolith: Khung Ra Quyết Định Cho Đội Ngũ Dưới 20 Người
Một đội mười hai kỹ sư ship một tính năng mới mỗi ba tuần. Build pipeline của họ chạy mười bảy service, việc deploy đòi hỏi phối hợp bốn repository, và thứ Ba tuần trước một endpoint user-preferences bị lỗi đã khiến luồng checkout ngưng hoạt động trong chín mươi phút. Trên giấy tờ, không có gì sai với kiến trúc của họ. Nó chỉ đơn giản là không phù hợp với họ. Việc lựa chọn giữa microservices và modular monolith hiếm khi là một câu hỏi thuần túy kỹ thuật. Đó là câu hỏi liệu chi phí vận hành của kiến trúc có tương xứng với quy mô và tốc độ của đội ngũ phải vận hành nó hay không.
Chi phí ẩn không nằm ở code, mà ở vận hành
Đối với một đội dưới hai mươi kỹ sư, code bạn viết để expose một capability qua HTTP là chuyện nhỏ so với mọi thứ bạn phải xây dựng xung quanh nó. Mỗi service cần deployment pipeline riêng, monitoring riêng, alerting rules riêng, on-call playbook riêng, câu chuyện database migration riêng, và contract test riêng với các service gọi đến nó. Phần plumbing này tốn xấp xỉ như nhau bất kể service làm một việc hay một trăm việc. Khi một đội nhỏ chạy một tá service, họ trả chi phí cố định đó mười hai lần, sử dụng chính những kỹ sư đáng lẽ phải đang xây dựng sản phẩm.
Modular monolith đảo ngược tỷ lệ này. Một deployment pipeline. Một luồng log. Một database với một dòng thời gian migration được chia sẻ. Các module giao tiếp thông qua các function call có kiểu (typed) mà fail tại compile time thay vì các JSON contract fail trong production lúc 3 giờ sáng. Kỷ luật giữ cho ranh giới module sạch sẽ vẫn là công việc thực sự, nhưng chi phí vận hành gần như bằng không. Kết quả là một đội mười người có thể vận hành một monolith có cấu trúc tốt gần như không cần đầu tư platform chuyên dụng, trong khi cùng đội đó chạy microservices thường sẽ có một hoặc hai kỹ sư bị hút hoàn toàn vào công việc infrastructure.
So sánh trực diện: năm quyết định mà hai lối kiến trúc phân kỳ rõ rệt
Cadence deployment
Microservices cho phép deploy độc lập trên lý thuyết. Trong thực tế, các đội nhỏ hiếm khi có các service thực sự độc lập. Một thay đổi ở pricing service thường cần release phối hợp với order service và invoicing service. Bạn thừa hưởng chi phí phối hợp của một monolith trong khi mất đi tính an toàn atomic-deploy của nó. Modular monolith deploy một lần. Toàn bộ hoặc là chạy hoặc là rollback cùng nhau. Đối với một đội ship hàng tuần thay vì hàng giờ, điều này thắng cả về tốc độ lẫn khả năng dự đoán.
Debugging
Khi một request trong hệ thống microservices fail, bạn phải theo dõi nó qua ba hoặc bốn hop của network, deserialization, retry, và timeout. Distributed tracing giúp được nhưng không thay thế được một stack trace. Trong một modular monolith, một stack trace duy nhất hiển thị toàn bộ đường call trên một màn hình. Đối với một đội nhỏ, giờ công kỹ sư dành cho debugging là nguồn lực khan hiếm nhất trong công ty. Bất cứ thứ gì giảm được nó sẽ chuyển hóa trực tiếp thành tốc độ ra tính năng, và điều đó chuyển hóa trực tiếp thành doanh thu.
Scaling
Lập luận cổ điển cho microservices là bạn có thể scale các component nóng một cách độc lập. Điều này thực sự đúng, nhưng hiếm khi liên quan ở quy mô mà một đội hai mươi người vận hành. Hầu hết các hệ thống bottleneck ở database trước khi bottleneck ở CPU của bất kỳ service đơn lẻ nào. Một monolith được thiết kế tốt với một read replica, một lớp caching, và một background job queue có thể xử lý nhiều traffic hơn mọi người nghĩ. Nếu bạn thực sự phát triển vượt quá nó, các ranh giới modular khiến việc tách một module nóng thành service riêng trở thành một refactor thẳng thắn, không phải viết lại từ đầu.
Tuyển dụng và onboarding
Một kỹ sư mới trong đội microservices dành hai tuần đầu để học deployment tooling, service catalog, các shared library, và mười hai nơi lưu trữ state. Trên một modular monolith, họ clone một repository, chạy một lệnh, và có thể trace bất kỳ hành vi nào end-to-end trong IDE của họ vào ngày thứ hai. Trong một thị trường tuyển dụng nơi kỹ sư senior đắt đỏ và thời gian đạt năng suất được đo bằng tuần, monolith giảm đáng kể chi phí onboarding. Đó là tiền bạn không phải chi, trên năng lực bạn có được sớm hơn.
Cô lập lỗi
Đây là điểm duy nhất microservices thực sự thắng. Một bug ở một service không trực tiếp làm crash các service khác. Nhưng đối với một đội nhỏ, "không crash" thường có nghĩa là "âm thầm trả về dữ liệu cũ hoặc sai", điều mà có thể lập luận là còn tệ hơn một outage rõ ràng vì mất nhiều thời gian hơn để phát hiện. Circuit breaker, timeout, retry với backoff, và graceful degradation không hề đơn giản để triển khai tốt, và các đội nhỏ hầu như luôn đầu tư dưới mức. Một monolith fail một cách ồn ào và hoàn toàn, điều này dễ alert hơn và dễ recover hơn.
Khi nào microservices thực sự đáng đồng tiền
Có ba pattern mà microservices kiếm được khoản thuế vận hành của chúng một cách xứng đáng. Thứ nhất, khi các phần khác nhau của hệ thống có đặc tính runtime thực sự khác nhau: một pipeline xử lý video real-time ngồi cạnh một CRUD admin panel phục vụ mười người dùng nội bộ. Thứ hai, khi các đội độc lập cần ship theo cadence độc lập mà không có chi phí phối hợp, điều này thường có nghĩa là bạn đã vượt qua ngưỡng 30 đến 40 kỹ sư và ranh giới tổ chức đã cứng lại. Thứ ba, khi một component đơn lẻ có yêu cầu scaling cứng, chẳng hạn như một endpoint ingestion event nhận hàng triệu request mỗi giờ, sẽ làm méo mó hình dạng tài nguyên của mọi thứ khác nếu đặt cùng chỗ.
Nếu không có điều kiện nào trong ba điều kiện đó áp dụng cho doanh nghiệp của bạn, microservices gần như chắc chắn đang tốn của bạn nhiều hơn số tiền tiết kiệm. Và cũng đáng để trung thực với chính mình về việc liệu chúng có thực sự áp dụng ngày hôm nay không, hay chúng chỉ có thể áp dụng trong một tương lai giả định nào đó có thể không bao giờ đến.
Modular monolith như một cây cầu
Kiến trúc dễ bào chữa nhất cho một đội dưới hai mươi người là một modular monolith với các ranh giới sạch sẽ, được thực thi giữa các module. Mỗi module sở hữu dữ liệu của riêng mình. Nó expose một interface hẹp, có kiểu ra cho các module khác. Nó không có các đọc database chéo module trực tiếp, không có dependency vòng, và không có shared mutable state rò rỉ qua ranh giới. Thực thi điều này bằng cấu trúc thư mục, package rule, linting, và code review. Xem mỗi vi phạm như một bug thực sự.
Làm tốt, điều này cho bạn sự đơn giản trong vận hành mà bạn cần ngày hôm nay và tính linh hoạt kiến trúc mà bạn có thể cần trong hai năm nữa. Khi ngày đến mà một module thực sự cần scale độc lập hoặc ship theo cadence riêng, việc tách nó ra trở thành một refactor cơ học chứ không phải một dự án khảo cổ kéo dài nhiều quý. Bạn nhận được lợi ích của microservices chỉ khi bạn thực sự cần chúng, và bạn chỉ trả tiền cho chúng vào lúc đó.
Kiến trúc đúng là kiến trúc mà đội của bạn có thể vận hành tốt ở quy mô hiện tại của bạn, với một đường tiến hóa rõ ràng cho quy mô mà bạn có thể đạt được. Đối với hầu hết các đội quy mô SME, đó là một modular monolith, không phải một hệ thống phân tán. Nếu bạn đang cân nhắc một cuộc thiết kế lại, hoặc bạn đã kế thừa một mớ microservices tràn lan đang làm chậm đội của bạn, một đối tác giàu kinh nghiệm có thể giúp bạn lập bản đồ bề mặt chi phí thực sự và thiết kế một cuộc migration không làm đình trệ roadmap của bạn. Đội MerkTechs đã đi con đường này với các khách hàng trong lĩnh vực e-commerce, fintech, và công cụ nội bộ, và chúng tôi sẵn lòng trao đổi về các đánh đổi cho tình huống cụ thể của bạn.