Micro-frontend Learning
Nền tảng

Khi nào nên và không nên dùng?

So sánh monolith, modular frontend và micro-frontend; đánh giá nhu cầu release độc lập, chi phí vận hành và cách thử nghiệm trước khi áp dụng.

Catalog đã làm xong bộ lọc mới, nhưng chưa thể lên production vì cart đang thay luồng giỏ hàng trong cùng bản release. Nếu tình huống này lặp lại, bạn có lý do để xem xét micro-frontend. Nhưng nếu điều khiến bạn khó chịu chỉ là một file quá dài hoặc build chậm, tách thành nhiều ứng dụng có thể chưa giải quyết đúng vấn đề.

Mặc định, mình chọn một frontend có module nghiệp vụ rõ ràng. Chỉ cân nhắc micro-frontend khi sự độc lập về phát hành hoặc migration tạo ra lợi ích cụ thể, và team có khả năng trả chi phí tích hợp, kiểm thử, vận hành đi kèm.

Phạm vi bài này

Bài này giúp bạn ra quyết định kiến trúc, không chọn giữa Module Federation, single-spa hay iframe. Ví dụ tiếp tục dùng shop với shell, catalog và cart; nếu chưa quen các tên này, bạn có thể đọc Micro-frontend là gì? trước.

Mục lục

Bạn đang muốn giải quyết vấn đề nào

Trước khi hỏi “dùng micro-frontend được không?”, hãy mô tả điểm nghẽn bằng một việc đang xảy ra. “Frontend lớn quá” chưa đủ cụ thể; “catalog phải đợi cart hoàn tất kiểm thử trước mỗi release, dù thay đổi không liên quan” thì có thể kiểm chứng.

Vấn đề quan sát đượcHướng xử lý nên thử trước
Component quá dài, khó hiểu ai phụ trách logic nàoTách component, module theo domain; làm rõ ownership và quy tắc import
Nhiều người sửa chung file, review khóĐiều chỉnh ranh giới module và trách nhiệm review; kiểm tra phần dùng chung có ôm quá nhiều logic không
Build hoặc CI chậmĐo bước chậm, tối ưu cache, phạm vi test và tác vụ bị chạy lại không cần thiết
Trang tải chậm, JavaScript lớnĐo hiệu năng ở trình duyệt; code splitting, lazy loading và giảm dependency
Các domain tương đối độc lập nhưng luôn phải đợi một release chungĐánh giá ranh giới deploy riêng và cơ chế composition
Cần thay một phần ứng dụng cũ mà không viết lại toàn bộĐánh giá migration theo route hoặc một phần nghiệp vụ có contract rõ

Các hướng này có thể kết hợp. Tuy vậy, mình sẽ sửa vấn đề ngay tại nơi nó xuất hiện trước khi thêm một lớp kiến trúc phân tán. Tách module giúp dễ hiểu code; tách đơn vị deploy giúp thay đổi cách phát hành. Hai việc liên quan nhưng không thay thế nhau.

So sánh ba cách tổ chức frontend

Trong bài này, frontend monolith chỉ một ứng dụng được build và deploy như một đơn vị. Nó không đồng nghĩa với code rối. Modular frontend vẫn có thể là một monolith ở cấp deployment, nhưng tổ chức code thành các module nghiệp vụ với giao diện công khai và ownership rõ.

Để so sánh, bảng dưới đây phân biệt một app có ranh giới nội bộ còn lỏng, một app được module hóa tốt và kiến trúc micro-frontend. Đây không phải ba bậc trưởng thành mà sản phẩm nào cũng phải đi qua.

Tiêu chíMột app, ranh giới còn lỏngModular frontendMicro-frontend
Đơn vị phát hànhCả ứng dụngThường vẫn là cả ứng dụngCác phần nghiệp vụ có artifact và đường phát hành riêng
Ranh giới codeDễ có import chéo và state dùng chungModule có API công khai, hạn chế truy cập nội bộNgoài ranh giới code còn có contract tích hợp giữa các đơn vị deploy
Làm một thay đổi xuyên domainDễ sửa trong cùng codebase, nhưng khó giới hạn ảnh hưởngPhối hợp qua API module; kiểm thử trong cùng bản buildCó thể cần thay đổi contract và kế hoạch tương thích nhiều version
Release một thay đổi nội bộ catalogBuild và deploy appBuild và deploy app; có thể tối ưu phạm vi CICó thể chỉ deploy catalog nếu contract tương thích
Kiểm thử và debugMột môi trường, nhưng phụ thuộc ngầm có thể khó lần raMột môi trường với ranh giới dễ theo dõi hơnPhải tái hiện cả tổ hợp version và lớp composition
Chi phí vận hànhÍt đơn vị cần quản lýÍt đơn vị deploy, thêm kỷ luật moduleNhiều artifact, pipeline, rollout, rollback và điểm tích hợp
Khi hợp lýPhạm vi nhỏ, cần triển khai ban đầu nhanhLựa chọn mặc định khi chưa cần release độc lậpKhi lợi ích tự chủ hoặc migration bù được chi phí phân tán

Bạn không cần chọn giữa “code nguyên khối” và “micro-frontend”. Modular frontend thường là phương án giữ được phần lớn lợi ích về tổ chức code mà chưa phải vận hành nhiều ứng dụng.

Cũng đừng lấy số repo làm tiêu chí: một monorepo có thể chứa nhiều đơn vị deploy độc lập, còn nhiều repo có thể vẫn bị buộc vào một bản release chung.

Khi nào micro-frontend đáng cân nhắc

Các team thật sự cần nhịp release riêng

Giả sử team catalog thường cập nhật tìm kiếm và bộ lọc, còn team cart đang làm một thay đổi dài hơi về giỏ hàng. Nếu các tính năng catalog giữ nguyên contract với cart mà vẫn phải chờ lịch release chung, ranh giới deploy riêng có thể giảm thời gian chờ đó.

Nhưng hãy tìm đúng nguyên nhân. Nếu catalog đang đợi backend cung cấp API mới hoặc một đợt duyệt nghiệp vụ, tách frontend không xóa được bước chờ. Nếu tổ chức vẫn bắt mọi phần deploy cùng một ngày dù kỹ thuật cho phép deploy riêng, lợi ích cũng chưa xuất hiện.

Không có một số lượng team cố định để bắt đầu dùng micro-frontend. Điều đáng hỏi là: một thay đổi nội bộ đã sẵn sàng có thể tới người dùng mà không bắt các team khác build, test và deploy lại phần không đổi hay không?

Ranh giới nghiệp vụ đủ độc lập

Catalog và cart là ứng viên hợp lý khi mỗi bên hoàn thành được phần lớn công việc của mình mà không sửa code nội bộ bên kia. Chúng vẫn giao tiếp, nhưng qua contract nhỏ và có ý nghĩa nghiệp vụ, chẳng hạn hành động thêm sản phẩm với productId và quantity, thay vì chia sẻ toàn bộ store.

Hãy xem các thay đổi gần đây. Nếu thêm một bộ lọc chỉ sửa catalog, đó là dấu hiệu tốt. Nếu mọi thay đổi catalog đều cần sửa cart và shell, có thể bạn đang đặt ranh giới sai hoặc để logic dùng chung quá rộng.

Vẫn sẽ có tính năng xuyên domain, như một chương trình khuyến mãi thay đổi cả cách hiển thị sản phẩm lẫn tổng tiền giỏ hàng. Micro-frontend không loại bỏ việc phối hợp này; mục tiêu là không biến phối hợp thành điều kiện bắt buộc cho mọi thay đổi nhỏ.

Cần migration từng phần

Một hệ thống cũ vẫn vận hành tốt phần giỏ hàng, nhưng catalog cần được xây lại. Bạn có thể chuyển /products sang ứng dụng mới, giữ /cart ở ứng dụng cũ và duy trì trải nghiệm chung qua routing, đăng nhập và design system.

Lợi ích ở đây là không phải viết lại toàn bộ sản phẩm trước khi đưa phần mới lên production. Trong giai đoạn chuyển tiếp, hai stack công nghệ có thể cùng tồn tại; đó là chi phí có chủ đích để giảm phạm vi migration, không phải lý do để thêm framework tùy ý.

Cần xác định đích đến từ đầu. Nếu sau migration bạn chỉ cần một app và một team vận hành, kiến trúc nhiều ứng dụng có thể là bước trung gian. Nếu các domain vẫn cần release riêng lâu dài, giữ micro-frontend mới có cơ sở.

Có khả năng vận hành các phần độc lập

Deploy riêng chỉ hữu ích khi team biết phần mình đang chạy version nào, phát hiện được lỗi và khôi phục được trạng thái an toàn. Có một remote tải thành công trong demo chưa chứng minh được những điều này.

Trước khi áp dụng, cần có người chịu trách nhiệm cho pipeline, artifact, contract và sự cố của từng phần. Các trách nhiệm xuyên suốt như auth, routing, design system, kiểm thử luồng mua hàng và cấu hình composition cũng cần owner; không nhất thiết phải lập một team platform riêng, nhưng không thể để chúng thành việc “ai rảnh thì làm”.

Deploy riêng nhưng luôn release cùng nhau

Nếu catalog và cart luôn phải lên cùng version, hoặc shell phải rebuild trong mọi lần đổi catalog, bạn chưa đạt mục tiêu phát hành độc lập của lộ trình này. Hãy kiểm tra contract, state dùng chung và cơ chế chọn artifact trước khi tách thêm ứng dụng.

Khi nào chưa nên dùng

Sản phẩm nhỏ hoặc nghiệp vụ còn thay đổi liên tục

Với một team đang tìm hướng sản phẩm, ranh giới nghiệp vụ hôm nay có thể thay vào tuần sau. Một app được module hóa cho phép di chuyển code và sửa luồng xuyên module trong cùng bản build; nhiều đơn vị deploy khiến việc tương tự kéo theo contract, pipeline và kế hoạch tương thích.

Mình sẽ giữ một ứng dụng, chia module theo những ranh giới đang hiểu và điều chỉnh khi nghiệp vụ rõ hơn. Một MVP không cần trả trước toàn bộ chi phí cho khả năng release độc lập chưa có nhu cầu sử dụng.

Một team vẫn có thể cần micro-frontend để migration ứng dụng cũ, nên “ít người” không phải lệnh cấm. Lý do áp dụng phải đến từ bài toán thực tế, không từ quy mô dự đoán.

Các phần phụ thuộc nhau trong hầu hết thay đổi

Nếu catalog đọc trực tiếp state nội bộ cart, cart import component nội bộ catalog và shell chứa logic nghiệp vụ của cả hai, thêm artifact riêng chưa làm chúng độc lập. Bạn chỉ chuyển một phần lỗi từ lúc build sang lúc chạy.

Trước hết, hãy làm rõ bên nào sở hữu dữ liệu và giao diện công khai nào cần được chia sẻ. Nếu nghiệp vụ thực sự không thể tách, giữ các phần đó trong cùng một module lớn hơn hoặc cùng đơn vị deploy có thể đúng hơn việc dựng nhiều remote rồi buộc release đồng bộ.

Mục tiêu chính là sửa build chậm hoặc bundle lớn

Build riêng có thể giảm phạm vi công việc trong một số thiết kế, nhưng cũng thêm bước build, publish và kiểm thử tích hợp. Muốn biết có lợi hay không, bạn phải đo pipeline hiện tại và điểm nghẽn cụ thể.

Với hiệu năng người dùng, micro-frontend có thể tải trùng framework, thêm request lấy entry và tạo chuỗi tải dependency. Lazy loading theo route cũng làm được trong một app; không cần deploy riêng chỉ để tránh tải code cart trên trang catalog.

Nếu mục tiêu là “nhanh hơn”, hãy nói rõ nhanh ở đâu: build, chờ release hay tải trang. Chúng là những mục tiêu khác nhau và có thể đánh đổi lẫn nhau.

Chỉ muốn nhiều repo hoặc nhiều framework

Tách repo giúp quản lý quyền truy cập và quy trình làm việc, nhưng không tự tạo khả năng release độc lập. Tương tự, nhiều team vẫn có thể làm việc trên cùng framework và cùng monorepo với ownership rõ.

Khác framework có thể hợp lý khi tích hợp hệ thống cũ hoặc migration từng phần. Còn nếu chỉ để mỗi team chọn stack yêu thích, sản phẩm sẽ phải trả chi phí dependency, tuyển dụng, hỗ trợ tooling và đồng bộ UX mà chưa chắc nhận được lợi ích tương ứng.

Backend dùng microservices cũng không bắt frontend phải chia theo từng service. UI nên bám vào trải nghiệm nghiệp vụ mà team có thể sở hữu, không nhất thiết ánh xạ một-một với cấu trúc backend.

Chi phí cần đưa vào quyết định

Khi tách catalog khỏi bản build chung, bạn mất một số đảm bảo trước đây được bundler và kiểm thử trong cùng app hỗ trợ. Các phần có thể đúng khi chạy riêng nhưng không tương thích khi ghép với version đang ở production.

Chi phíCâu hỏi cần trả lời trước khi mở rộng
Contract và versionCatalog mới có chạy được với shell và cart hiện tại không? Tab đang mở code cũ có còn hoạt động sau rollout không?
Kiểm thử tích hợpAi kiểm tra luồng xem sản phẩm → thêm vào giỏ → mở cart với các phần đang được ghép?
Rollout và rollbackCó chọn lại artifact cũ mà không deploy phần khác không? API và dữ liệu có còn tương thích với bản cũ không?
Local developmentDeveloper có chạy một phần với bản ổn định của phần còn lại được không? Có cách tái hiện tổ hợp version lỗi không?
Hiệu năngCó tải trùng dependency hoặc thêm chuỗi request không? Hiệu năng trên thiết bị và mạng mục tiêu thay đổi thế nào?
ObservabilityKhi luồng mua hàng lỗi, có biết lỗi thuộc catalog, cart, shell hay backend và đang chạy version nào không?
UX và CSSAi duy trì navigation, focus, thông báo lỗi, accessibility và ngăn style một phần ảnh hưởng phần khác?
SecurityCode được tải đến từ đâu, có quyền gì? Backend kiểm tra quyền ra sao? Cần isolation thực sự hay chỉ tách ownership?

Đừng mặc định “catalog lỗi thì cart vẫn an toàn”. Các phần chạy cùng trang có thể dùng chung main thread, global JavaScript và CSS. Fallback giúp với một số lỗi tải hoặc render; nó không ngăn mọi tác động xuyên ứng dụng, và tách bundle không tự tạo security boundary.

Chi phí cũng không chỉ nằm ở giai đoạn setup. Contract dùng lâu dài cần quản lý thay đổi; design system cần chính sách cập nhật; các pipeline cần được duy trì. Lợi ích release riêng phải đủ đáng giá để nuôi những việc này sau khi demo đã hoàn thành.

Ba tình huống với shop mẫu

Các tình huống dưới đây là giả định để luyện quyết định, không phải số liệu từ một dự án thực tế.

Tình huốngMình sẽ chọnVì sao
Một team làm shop mới; catalog và cart đổi cùng nhau khi nghiệp vụ được chốtModular frontend, một đơn vị deployRanh giới còn thay đổi; release riêng chưa giải quyết điểm nghẽn nào
Catalog và cart có owner, nhịp release riêng; thay đổi nội bộ thường không liên quan nhưng bị lịch release chung chặnThử micro-frontend với một ranh giới theo routeCó lợi ích cụ thể để kiểm chứng; chưa cần tách cả ứng dụng cùng lúc
Catalog cũ cần viết lại, cart tiếp tục vận hành; có thể giữ contract đăng nhập và luồng thêm vào giỏCân nhắc micro-frontend phục vụ migrationCho phép thay từng phần; cần ghi rõ sẽ giữ hay gỡ lớp composition sau migration

Với tình huống thứ hai, đừng vội tách ProductCard, FilterPanel và nút “Thêm vào giỏ” thành ba remote. Ranh giới catalog đủ trọn vẹn để team sở hữu thường dễ vận hành hơn những ranh giới nhỏ buộc nhiều phần phối hợp mỗi lần sửa giao diện.

Với tình huống thứ ba, nếu chỉ cần chuyển nguyên một route sang hệ thống mới, routing ở server hoặc reverse proxy có thể đủ. Không nhất thiết phải ghép nhiều framework trong cùng trang khi yêu cầu sản phẩm không cần điều đó.

Checklist trước khi chọn kiến trúc

Checklist này không phải thang điểm: đánh dấu nhiều ô không tự động có nghĩa là nên dùng. Nó giúp bạn tìm bằng chứng còn thiếu trước khi thử một ranh giới deploy.

  • Có ví dụ cụ thể về việc chờ release chung hoặc khó migration, không chỉ nhận xét “frontend lớn”.
  • Đã phân biệt điểm nghẽn do frontend với chờ API, duyệt nghiệp vụ hoặc quy trình release của tổ chức.
  • Đã xem xét cách đơn giản hơn: module hóa, làm rõ ownership, tối ưu CI hoặc lazy loading.
  • Chỉ ra được phạm vi nghiệp vụ mà phần lớn thay đổi không cần sửa phần khác.
  • Mỗi phần và lớp composition có owner rõ, bao gồm trách nhiệm khi lỗi ở production.
  • Contract cho routing, dữ liệu, auth và giao tiếp có kế hoạch giữ tương thích.
  • Có cách chọn artifact mới và quay về bản cũ mà không buộc rebuild các phần không đổi.
  • Có kiểm thử luồng người dùng xuyên các phần, theo dõi lỗi và nhận diện version đang chạy.
  • Có baseline và tiêu chí đánh giá cả lợi ích release lẫn hiệu năng, độ ổn định của sản phẩm.

Một cách đọc nhanh kết quả:

direction: down
need: "Cần release riêng hoặc migration\ntừng phần?" {shape: diamond}
boundary: "Ranh giới và ownership rõ?" {shape: diamond}
ops: "Khả năng vận hành phù hợp?" {shape: diamond}
modular: "Giữ modular frontend"
clarify: "Làm rõ module, contract, trách\nnhiệm"
prepare: "Chuẩn bị vận hành trước khi thử"
pilot: "Thử một đơn vị deploy\nĐo kết quả trước khi mở rộng"
need -> modular: "Chưa"
need -> boundary: "Có"
boundary -> clarify: "Chưa"
boundary -> ops: "Rõ"
ops -> prepare: "Chưa"
ops -> pilot: "Phù hợp"
Cần release riêng hoặc migrationtừng phần?Ranh giới và ownership rõ?Khả năng vận hành phù hợp?Giữ modular frontendLàm rõ module, contract, tráchnhiệmChuẩn bị vận hành trước khi thửThử một đơn vị deployĐo kết quả trước khi mở rộng Chưa Có Chưa Rõ Chưa Phù hợp
Cần release riêng hoặc migrationtừng phần?Ranh giới và ownership rõ?Khả năng vận hành phù hợp?Giữ modular frontendLàm rõ module, contract, tráchnhiệmChuẩn bị vận hành trước khi thửThử một đơn vị deployĐo kết quả trước khi mở rộng Chưa Có Chưa Rõ Chưa Phù hợp
Checklist trước khi chọn kiến trúc

Nếu đang học, bạn vẫn có thể làm demo shell–catalog–cart để hiểu cơ chế. Chỉ cần tách quyết định “thử công cụ để học” khỏi quyết định “đưa kiến trúc này vào production”.

Thử một ranh giới trước khi tách toàn bộ

Ghi lại hiện trạng và mục tiêu

Chọn một phạm vi đủ trọn vẹn và có lợi ích kiểm chứng được, chẳng hạn catalog theo route /products. Trước khi đổi kiến trúc, ghi lại một số lần release gần đây: bước nào phải chờ, ai phải tham gia và thời gian nằm ở đâu.

Đừng chỉ đo thời gian build. Nếu giả thuyết là giảm chờ team khác, cần quan sát khoảng thời gian từ khi tính năng sẵn sàng đến khi người dùng nhận được nó, cùng các lý do chờ. Đồng thời lưu baseline hiệu năng và lỗi của luồng mua hàng để tránh đánh đổi mà không biết.

Một ghi chú quyết định ngắn có thể dùng mẫu sau; đây là ví dụ, không phải cấu hình của framework:

Vấn đề: Catalog bị chặn bởi release cart dù contract không đổi.
Phương án đơn giản đã xem xét: Module hóa và tối ưu phạm vi CI.
Phạm vi thử: Catalog ở /products; cart giữ nguyên.
Owner: Team catalog; owner shell chịu trách nhiệm composition.
Contract cần giữ: Route, phiên đăng nhập, hành động thêm vào giỏ.
Lợi ích cần kiểm chứng: Release nội bộ catalog không buộc cart/shell deploy.
Ràng buộc: Luồng mua hàng đúng; hiệu năng trong ngân sách đã thống nhất.
Cách dừng: Giữ giao diện module rõ để có thể ghép lại lúc build.

Ngân sách hiệu năng và mức cải thiện mong muốn phải do dự án xác định từ baseline, không lấy một ngưỡng chung áp cho mọi sản phẩm.

Kiểm chứng bằng một thay đổi thật

Thử release một thay đổi nội bộ catalog, chẳng hạn thêm tùy chọn sắp xếp nhưng không đổi contract. Bài kiểm chứng không kết thúc ở việc remote tải được; bạn cần thấy điều gì xảy ra ở cả đường phát hành lẫn luồng người dùng.

  1. Build, test và publish artifact catalog mà không build lại cart hay shell.
  2. Đưa catalog mới vào tổ hợp đang chạy bằng cơ chế chọn version đã thiết kế; nếu mọi lần đổi version vẫn buộc rebuild shell, kiểm tra lại mục tiêu độc lập.
  3. Kiểm tra từ xem sản phẩm tới thêm vào giỏ và mở cart với các version hiện tại.
  4. Kiểm tra khi catalog không tải được và cách hệ thống báo lỗi; xác nhận người dùng không bị một màn hình trắng không có hướng xử lý.
  5. Chọn lại artifact catalog cũ, xác nhận rollback không cần deploy cart và các API vẫn hỗ trợ version cũ.
  6. Theo dõi hiệu năng, lỗi và dấu vết version; xem cả phiên đã mở trước rollout vì chúng có thể tiếp tục chạy code cũ.

Rollback frontend không rollback mọi thứ

Chọn lại JavaScript cũ không hoàn tác thay đổi backend, dữ liệu hoặc side effect đã xảy ra. Thử nghiệm phải giữ API tương thích và có kế hoạch riêng cho những thay đổi không thể đảo ngược bằng cách đổi artifact.

Quyết định tiếp tục hay dừng

Tiếp tục khi thử nghiệm cho thấy thay đổi nội bộ đi tới người dùng với ít phối hợp bắt buộc hơn, team vận hành được phần đã tách và chất lượng sản phẩm vẫn đáp ứng tiêu chí đã đặt. Sau đó mới xem ranh giới tiếp theo có cùng lợi ích hay không; một pilot thành công không chứng minh mọi component nên thành remote.

Dừng hoặc điều chỉnh khi bạn vẫn phải release đồng bộ, dành nhiều công sức chữa contract hơn công sức tiết kiệm được, hoặc chưa quan sát và khôi phục lỗi an toàn. Tùy nguyên nhân, hướng xử lý có thể là sửa ranh giới, giảm state dùng chung hoặc trở lại composition lúc build trong một modular frontend.

Bước tiếp theo cho hệ thống của bạn là chọn một thay đổi gần đây của catalog và liệt kê tất cả phần phải sửa, build, test, deploy để đưa nó lên production. Danh sách đó sẽ cho biết bạn đang thiếu ranh giới code, ranh giới phát hành hay chỉ cần cải thiện quy trình hiện tại.

Đọc tiếp

Tài liệu tham khảo

On this page