Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là BA của một sàn thương mại điện tử. Cùng một sản phẩm, nhưng web hiển thị rất nhiều: mô tả dài, gallery 20 ảnh, đánh giá chi tiết, bảng so sánh thông số. Còn app điện thoại trên màn hình 6 inch chỉ cần tên, giá, 1 ảnh thumbnail và nút "Mua ngay". Vậy mà cả hai đang gọi cùng một API để lấy dữ liệu sản phẩm. Hậu quả: app mobile tải về một cục JSON nặng 80 KB cho mỗi sản phẩm trong khi nó chỉ cần dùng 4 KB. Trên mạng 3G ở vùng quê, màn hình danh sách 20 sản phẩm tải mất 6 giây. Khách bỏ giỏ hàng.
Đây chính là vấn đề mà BFF — Backend for Frontend sinh ra để giải quyết. Đây là một mẫu kiến trúc (architecture pattern) mà ngày càng nhiều sản phẩm số ở Việt Nam và Đông Nam Á áp dụng: Grab, Shopee, MoMo, Tiki đều có dạng kiến trúc này ở các mức độ khác nhau.
Là một BA, bạn không phải người code BFF. Nhưng bạn là người ngồi giữa: bạn nghe team mobile than "API trả về quá nhiều thứ không dùng", nghe team web phàn nàn "phải gọi 5 API mới ghép được một màn hình", và nghe team backend bảo "chúng tôi không thể vừa lòng tất cả mọi người được". BFF chính là câu trả lời cho ba lời than đó. Hiểu pattern này giúp bạn viết spec đúng chỗ, đặt đúng câu hỏi khi review thiết kế, và tránh đưa ra yêu cầu kiểu "làm một API chung cho mọi nền tảng" — một yêu cầu nghe thì hợp lý nhưng thực tế lại gây ra rất nhiều đau khổ.
Khái niệm cốt lõi
BFF là gì
BFF là một lớp backend riêng được xây cho từng loại frontend (cho từng "trải nghiệm người dùng"). Thay vì có một API tổng đáp ứng tất cả client, bạn có nhiều BFF nhỏ: một BFF cho web, một BFF cho mobile, đôi khi thêm một BFF cho smart TV hay cho đối tác bên ngoài.
Mỗi BFF đứng giữa frontend và các dịch vụ backend phía sau (các microservices như service sản phẩm, service đơn hàng, service người dùng…). Nhiệm vụ của nó là:
- Gọi nhiều service phía sau, ghép dữ liệu lại (aggregation).
- Cắt gọt, định dạng dữ liệu sao cho vừa khít với nhu cầu của riêng frontend đó.
- Giấu đi sự phức tạp của hệ thống bên trong khỏi frontend.
Vì sao "một API cho tất cả" lại không ổn
Một API dùng chung (general-purpose API) thường rơi vào hai cái bẫy:
Bẫy over-fetching (lấy thừa): API trả về mọi trường có thể có, để client nào cũng dùng được. Mobile phải tải về cả đống dữ liệu nó vứt đi. Lãng phí băng thông, tốn pin, chậm.
Bẫy chatty client (gọi quá nhiều lần): Vì API được thiết kế theo từng resource riêng lẻ (sản phẩm, tồn kho, khuyến mãi, đánh giá là 4 endpoint khác nhau), để dựng một màn hình chi tiết sản phẩm, app phải gọi 4–5 lượt request. Mỗi lượt lại tốn thời gian thiết lập kết nối. Trên mobile network có độ trễ cao, gọi nhiều lần là kẻ thù của tốc độ.
BFF giải quyết cả hai: nó gọi 4–5 service đó ở phía server (nơi mạng nhanh và ổn định), ghép lại, rồi trả về cho mobile đúng một response gọn gàng trong một lần.
Phân biệt BFF với API Gateway
Đây là chỗ BA hay nhầm, nên cần làm rõ. API Gateway (đã học ở Bài 42–43) là một cổng chung cho toàn bộ traffic: nó lo xác thực, rate limiting, routing, ghi log — nhưng nó không cắt gọt dữ liệu theo từng frontend. Gateway trung lập với client.
BFF thì thiên vị có chủ đích: mỗi BFF chỉ phục vụ một frontend và được phép biết rất rõ frontend đó cần gì. BFF chứa logic riêng của trải nghiệm (presentation logic), còn Gateway chứa logic chung của hạ tầng (cross-cutting concerns).
Trong thực tế nhiều hệ thống dùng cả hai: client → API Gateway → BFF → microservices. Gateway lo bảo mật và phân luồng; BFF lo nắn dữ liệu cho vừa frontend.
Ranh giới chia BFF theo cái gì
Nguyên tắc vàng do Sam Newman (người phổ biến thuật ngữ này) đưa ra: chia BFF theo trải nghiệm người dùng, không phải theo loại thiết bị. Nghĩa là nếu web và mobile dùng chung một team và một nhu cầu dữ liệu gần như giống nhau, đừng máy móc tách hai BFF. Ngược lại, nếu cùng là "web" nhưng có web cho khách hàng và web cho admin với nhu cầu khác hẳn, thì tách thành hai BFF lại hợp lý. Ranh giới nên đi theo team sở hữu trải nghiệm đó.
Tình huống thực tế
Ví dụ 1: Sàn TMĐT "ChợViệt" — màn hình chi tiết sản phẩm
Bối cảnh: ChợViệt (giả định) có web và app mobile. Màn hình chi tiết sản phẩm cần: thông tin sản phẩm, giá hiện tại, tồn kho, khuyến mãi đang chạy, 5 đánh giá mới nhất, và gợi ý sản phẩm liên quan. Sáu mẩu dữ liệu này nằm ở 6 microservices khác nhau.
Trước khi có BFF: app mobile tự gọi 6 request. Trên 4G ổn định mất ~1,2 giây; trên 3G yếu mất tới 4–5 giây vì độ trễ cộng dồn. Đội mobile còn phải viết code ghép dữ liệu ngay trên điện thoại, dễ lỗi và khó cập nhật (phải chờ user update app).
Sau khi có Mobile BFF: app gọi một endpoint /mobile/product/{id}. BFF ngồi trong data center, gọi song song 6 service (mạng nội bộ nhanh, độ trễ chỉ vài mili giây), ghép lại, cắt bỏ những trường mobile không hiển thị (ví dụ bảng thông số kỹ thuật 40 dòng — mobile chỉ cần 5 dòng), nén gọn còn ~5 KB và trả về. Thời gian tải màn hình trên 3G giảm từ ~4,5 giây xuống còn ~1,3 giây.
Bài học rút ra: BFF dời việc "gọi nhiều và ghép dữ liệu" từ thiết bị yếu (điện thoại, mạng kém) vào server mạnh (mạng nội bộ nhanh). Đó là lý do số một khiến mobile cực kỳ hưởng lợi từ BFF.
Ví dụ 2: Ứng dụng gọi xe kiểu Grab — màn hình bản đồ realtime
Bối cảnh: một app gọi xe (giả định tên "GoNhanh") có app cho hành khách, app cho tài xế, và một web dashboard cho đội vận hành. Cùng dữ liệu "chuyến đi", nhưng ba trải nghiệm cần ba thứ rất khác nhau:
- App hành khách: vị trí tài xế, ETA, giá, biển số xe.
- App tài xế: địa chỉ đón, lộ trình, tiền công, nút nhận/từ chối chuyến.
- Dashboard vận hành: toàn bộ chuyến đang chạy trong thành phố, cảnh báo chuyến quá lâu, thống kê.
Giải pháp: ba BFF riêng. Passenger BFF chỉ trả về đúng trường hành khách được phép thấy. Driver BFF tối ưu cho việc cập nhật vị trí liên tục, định dạng nhẹ để tiết kiệm pin. Ops BFF tổng hợp dữ liệu toàn thành phố cho dashboard. Mỗi team frontend làm việc với BFF của mình, không giẫm chân nhau.
Bài học rút ra: BFF không chỉ về hiệu năng, mà còn về kiểm soát dữ liệu lộ ra và tốc độ phát triển độc lập của từng team. Mỗi team đổi BFF của mình mà không sợ làm vỡ frontend của team khác.
Ví dụ 3: Ví điện tử — khi BFF bị lạm dụng
Bối cảnh: một ví điện tử (giả định "PayViet") ban đầu tách BFF rất đúng: Web BFF và Mobile BFF. Nhưng sau đó mỗi khi có tính năng mới, đội backend lại copy logic nghiệp vụ tính phí, kiểm tra hạn mức giao dịch vào cả hai BFF cho nhanh. Sau một năm, logic tính phí bị lặp ở 2 nơi, và khi luật thay đổi mức phí, team sửa Mobile BFF mà quên Web BFF. Hậu quả: khách dùng web bị tính sai phí trong 3 ngày trước khi bị phát hiện.
Bài học rút ra: BFF chỉ nên chứa logic trình bày (lấy gì, ghép thế nào, cắt gọt ra sao cho frontend). Logic nghiệp vụ cốt lõi (tính phí, kiểm tra hạn mức) phải nằm ở service backend dùng chung, không được nhét vào BFF. Khi review thiết kế, BA nên cảnh giác câu hỏi: "Logic này nếu trùng giữa hai BFF thì có nguy hiểm khi sửa lệch nhau không?"
Hướng dẫn từng bước
Khi bạn là BA tham gia một dự án cân nhắc dùng BFF, đây là cách tiếp cận:
Bước 1 — Liệt kê các loại frontend và nhu cầu dữ liệu của từng cái. Lập một bảng: cột là các frontend (web khách, app khách, app nội bộ…), hàng là các màn hình chính. Ô giao nhau ghi rõ màn hình đó cần dữ liệu gì. Bảng này lập tức lộ ra sự khác biệt giữa các frontend — căn cứ để quyết định có cần BFF không.
Bước 2 — Đo độ "lệch" giữa các frontend. Nếu web và mobile cần dữ liệu gần như y hệt, một API chung là đủ, đừng vẽ vời BFF cho phức tạp. BFF chỉ đáng làm khi các frontend lệch nhau rõ rệt về dữ liệu, số lần gọi, hoặc ràng buộc hiệu năng.
Bước 3 — Xác định màn hình "nặng". Tìm những màn hình phải gọi nhiều API mới dựng được (chatty), hoặc tải về nhiều dữ liệu thừa (over-fetching). Đây là ứng cử viên số một để BFF gom lại thành một endpoint.
Bước 4 — Vẽ ranh giới BFF theo team/trải nghiệm. Đề xuất mỗi BFF gắn với một team sở hữu một trải nghiệm. Ghi rõ trong spec: BFF nào phục vụ frontend nào.
Bước 5 — Chốt "luật bất di bất dịch": BFF không chứa logic nghiệp vụ cốt lõi. Viết hẳn nguyên tắc này vào tài liệu thiết kế để tránh bẫy ở Ví dụ 3. Liệt kê rạch ròi: cái gì thuộc BFF (presentation), cái gì phải ở service backend (business rule).
Bước 6 — Định nghĩa hợp đồng (contract) cho từng endpoint BFF. Với mỗi endpoint, ghi rõ: frontend nào dùng, trả về những trường gì, ghép từ service nào. Đây là phần BA đóng góp giá trị lớn nhất — biến nhu cầu trải nghiệm thành đặc tả dữ liệu cụ thể.
Lỗi thường gặp & mẹo
Lỗi 1 — Nhồi logic nghiệp vụ vào BFF. Như đã thấy ở PayViet. Mẹo: mỗi khi thấy một quy tắc tính toán xuất hiện trong BFF, hỏi "nếu có frontend thứ ba, quy tắc này có phải lặp lại không?". Nếu có, nó thuộc về service chung.
Lỗi 2 — Tạo quá nhiều BFF. Tách BFF theo từng thiết bị nhỏ (iPhone riêng, Android riêng, iPad riêng) là quá đà, gây bùng nổ số lượng dịch vụ phải bảo trì. Mẹo: gộp các frontend có nhu cầu giống nhau vào chung một BFF. Bắt đầu ít, tách thêm khi thực sự thấy đau.
Lỗi 3 — Code trùng lặp giữa các BFF. Hai BFF thường cần vài thao tác giống nhau (ví dụ cùng gọi service user). Mẹo: tách phần dùng chung thành thư viện/service riêng để tái sử dụng, đừng copy-paste.
Lỗi 4 — Quên BFF cũng là một điểm có thể chết (single point of failure) cho frontend đó. Nếu Mobile BFF sập, cả app mobile tê liệt. Mẹo: trong spec phải có yêu cầu về độ sẵn sàng, fallback, và xử lý khi một service phía sau lỗi (BFF nên trả về phần dữ liệu lấy được thay vì hỏng toàn bộ màn hình).
Lỗi 5 — BFF biến thành "bãi rác" gánh mọi thứ. Theo thời gian, người ta nhét đủ thứ vào BFF cho tiện. Mẹo: định kỳ review, giữ BFF mỏng và chỉ làm đúng vai trò ghép + nắn dữ liệu.
Mẹo bonus cho BA: Khi nghe team mobile than "API trả thừa" và team web than "phải gọi quá nhiều API", đừng vội yêu cầu sửa API chung cho cả hai vui — đó thường là dấu hiệu kinh điển cho thấy đã đến lúc đề xuất BFF.
Bài tập thực hành
- Lập bảng nhu cầu dữ liệu: Chọn một app bạn hay dùng (Shopee, MoMo, Tiki…). Lấy màn hình "chi tiết đơn hàng". Liệt kê những dữ liệu app mobile hiển thị, rồi tưởng tượng phiên bản web của cùng màn hình đó hiển thị thêm những gì. Hai danh sách khác nhau ở đâu? Sự khác biệt đó có đủ lớn để biện minh cho hai BFF không?
- Phát hiện màn hình "chatty": Chọn một màn hình bạn nghĩ cần gom dữ liệu từ nhiều nguồn (ví dụ trang chủ một app TMĐT: banner, danh mục, flash sale, gợi ý cá nhân hóa). Liệt kê các "mẩu" dữ liệu và đoán xem mỗi mẩu đến từ service nào. Nếu không có BFF, app phải gọi bao nhiêu lần? Viết một câu mô tả endpoint BFF gom tất cả lại.
- Phân loại logic: Cho 5 quy tắc sau, hãy xếp mỗi cái vào "thuộc BFF" hay "thuộc service backend": (a) ẩn số điện thoại tài xế khỏi response của hành khách, (b) tính phí giao hàng theo khoảng cách, (c) ghép tên sản phẩm với ảnh thumbnail cho danh sách, (d) kiểm tra số dư ví trước khi cho thanh toán, (e) định dạng ngày giờ theo múi giờ Việt Nam để hiển thị. Giải thích lý do cho mỗi lựa chọn.
- Viết một đoạn spec ngắn: Giả sử bạn là BA cho dashboard vận hành của một app gọi xe. Viết 5–7 dòng spec cho endpoint
GET /ops/active-trips: nó phục vụ frontend nào, trả về những trường gì, ghép từ service nào, và có ràng buộc bảo mật gì.
Tóm tắt
- BFF (Backend for Frontend) là một lớp backend riêng được xây cho từng loại frontend/trải nghiệm, nằm giữa frontend và các microservices phía sau.
- Nó giải hai nỗi đau kinh điển: over-fetching (tải dữ liệu thừa) và chatty client (gọi quá nhiều request) — bằng cách gọi và ghép dữ liệu ở phía server, rồi trả về đúng những gì frontend cần.
- BFF khác API Gateway: Gateway trung lập, lo hạ tầng chung; BFF thiên vị có chủ đích, lo nắn dữ liệu cho một frontend. Thực tế hệ thống thường dùng cả hai.
- Chia BFF theo trải nghiệm/team sở hữu, không theo từng thiết bị. Bắt đầu ít, tách thêm khi thực sự cần.
- Luật quan trọng nhất: BFF chỉ chứa logic trình bày, không chứa logic nghiệp vụ cốt lõi — nếu không sẽ trùng lặp và lệch nhau giữa các BFF, gây lỗi nghiêm trọng (như tính sai phí ở ví dụ ví điện tử).
- Vai trò BA: lập bảng nhu cầu dữ liệu từng frontend, phát hiện màn hình nặng, vẽ ranh giới BFF, và định nghĩa hợp đồng dữ liệu cho từng endpoint — đó là nơi BA đóng góp giá trị rõ rệt nhất.