- GraphQL cố gắng giải quyết vấn đề yêu cầu thừa dữ liệu, nhưng trong phần lớn môi trường doanh nghiệp, đây đã là vấn đề được giải bằng cách khác
- Trong các hệ thống doanh nghiệp nơi kiến trúc BFF (Backend for Frontend) đã trở nên phổ biến, lợi thế chính của GraphQL giảm đi đáng kể
- Độ phức tạp khi triển khai, khả năng quan sát suy giảm, vấn đề cache, ràng buộc về ID, sự bất tiện khi xử lý tệp khiến chi phí trong môi trường vận hành thực tế tăng cao
- REST đơn giản, nhanh và dễ xử lý lỗi cũng như onboarding, nên hiệu quả hơn trong môi trường đội ngũ quy mô lớn
- Kết luận là GraphQL hữu ích trong một số tình huống cụ thể, nhưng là một lựa chọn quá mức đối với phần lớn doanh nghiệp
Vấn đề mà GraphQL muốn giải quyết
- Mục tiêu cốt lõi của GraphQL là ngăn overfetching (yêu cầu thừa dữ liệu không cần thiết)
- Client chỉ yêu cầu các trường cần thiết để giảm việc truyền dữ liệu dư thừa
- Có ưu điểm là không cần sửa backend mỗi khi phát sinh yêu cầu UI mới
- Tuy nhiên, trong môi trường thực tế, cấu trúc lý tưởng này không khớp với hiện thực phức tạp
Overfetching đã được giải quyết bằng BFF
- Phần lớn frontend doanh nghiệp sử dụng lớp BFF (Backend for Frontend)
- Kết hợp dữ liệu theo đúng nhu cầu UI, hợp nhất nhiều lời gọi downstream và che giấu độ phức tạp của backend
- BFF dựa trên REST vốn đã có thể chỉ trả về dữ liệu cần thiết, nên ưu điểm của GraphQL bị trùng lặp
- Nếu lớp GraphQL lấy dữ liệu từ REST API, thì overfetching chỉ đơn giản chuyển xuống thấp hơn một tầng
- GraphQL có thể hữu ích khi nhiều trang dùng chung một endpoint, nhưng
- lợi ích đó chỉ ở mức chấp nhận thêm gánh nặng cấu hình và bảo trì để đổi lấy việc tiết kiệm vài kilobyte
Độ phức tạp triển khai và suy giảm năng suất
- GraphQL mất nhiều thời gian triển khai hơn và phức tạp hơn REST
- Cần thêm công việc như định nghĩa schema, type, resolver, data source
- Tồn tại gánh nặng duy trì sự đồng bộ giữa schema và client
- GraphQL tối ưu cho tiêu thụ (sự thuận tiện của client) nhưng đánh đổi bằng sản xuất (tốc độ phát triển server)
- Trong môi trường doanh nghiệp, tốc độ phát triển và sự đơn giản quan trọng hơn
Vấn đề về khả năng quan sát và giám sát
- Cơ chế mã trạng thái HTTP của GraphQL không nhất quán
- Ngay cả phản hồi 200 cũng có thể chứa lỗi, khiến việc phân biệt thành công/thất bại khi giám sát trở nên khó khăn
- REST phân tách rõ ràng bằng 2XX/4XX/5XX nên việc lọc trên dashboard trực quan hơn
- Có thể tùy biến bằng Apollo v.v., nhưng điều này lại gây ra thiết lập bổ sung và gánh nặng tinh thần
- Khi xử lý sự cố trong vận hành, việc xác định vấn đề khó và phức tạp hơn REST
Giới hạn thực tế của cache
- Normalized caching của Apollo mạnh về lý thuyết nhưng trong thực tế lại mong manh và phức tạp
- Những query chỉ khác một trường cũng bị xem là riêng biệt, nên cần liên kết thủ công
- Việc debug cache trở thành một vấn đề riêng
- Ngược lại, REST chỉ cần cache toàn bộ phản hồi nên ổn định và dễ bảo trì
Vấn đề với ràng buộc trường ID
- Apollo giả định mọi object đều có trường
id hoặc _id
- Nhiều API doanh nghiệp không có ID duy nhất, hoặc không phải định danh toàn cục
- Để khớp với giả định này, BFF phải thêm logic tạo ID cục bộ
- Kết quả là tăng thêm trường và logic không cần thiết, làm triệt tiêu hiệu quả giảm overfetching
Sự kém hiệu quả của upload và download tệp
- GraphQL không phù hợp để xử lý dữ liệu nhị phân
- Trên thực tế thường trả về URL tải xuống, còn tệp thì được truyền qua REST
- Nếu đưa dữ liệu lớn như PDF vào phản hồi GraphQL sẽ gây suy giảm hiệu năng
- Vì vậy, lý tưởng về một “API duy nhất” của GraphQL bị phá vỡ
Onboarding và đường cong học tập
- Phần lớn lập trình viên đã có nhiều kinh nghiệm với REST, còn GraphQL thì cần học thêm
- Cần nắm các khái niệm mới như schema, resolver, cách tạo query, quy tắc cache, xử lý lỗi
- Điều này dẫn đến tốc độ onboarding của đội ngũ giảm xuống
- REST là cách tiếp cận “nhàm chán nhưng có khả năng mở rộng cao”, nên phù hợp với các đội ngũ lớn
Độ phức tạp của xử lý lỗi
- Phản hồi lỗi của GraphQL phức tạp với trường nullable, partial data, mảng
errors, mã trạng thái mở rộng
- Cần lần theo resolver nào đã thất bại
- REST chỉ cần phân biệt bằng 400/500 nên dễ hiểu và dễ debug hơn
Kết luận: GraphQL là công nghệ ngách
- GraphQL là một công cụ hiệu quả trong một số tình huống cụ thể
- Nhưng trong phần lớn môi trường doanh nghiệp, vấn đề đã được giải bằng BFF và REST
- Thách thức chính không phải là overfetching mà là khả năng quan sát, độ tin cậy và tốc độ
- Kết quả là GraphQL giải quyết một vấn đề hẹp nhưng lại tạo ra độ phức tạp rộng hơn
- Kết luận là: “GraphQL không tệ, nhưng trong đa số trường hợp thì không cần thiết”
2 bình luận
Dù có cả ưu và nhược điểm, nhưng việc thiết kế dữ liệu và kiểm soát truy cập ở cấp độ schema, rồi mỗi khi thêm thứ gì đó lại phải thêm vào REST API, như vậy có vẻ vẫn tốt hơn là để sau này cuối cùng trả về tất cả. Nhược điểm thì rõ ràng, nhưng ưu điểm cũng rất rõ ràng nên haha
Ý kiến trên Hacker News
Tôi không đồng ý với bài viết cho rằng vấn đề chính của GraphQL là overfetching
Theo tôi, lợi thế thực sự là (a) nó ép buộc một hợp đồng dựa trên kiểu chặt chẽ, và (b) tiến hóa schema dễ hơn nhiều
Nhờ hệ thống kiểu, input và output luôn tuân theo hình dạng đã được định nghĩa, và nếu dùng scalar type tùy chỉnh (ví dụ: số điện thoại, email, v.v.) thì có thể giảm đáng kể bug và vấn đề bảo mật
Ngoài ra, việc thêm field mới hay loại bỏ field cũ đều đã được chuẩn hóa, nên cả server lẫn client đều có ít gánh nặng nhận thức hơn
Lý do tôi dùng GraphQL là vì khả năng tổ hợp và tiến hóa API. Đặc biệt trong các hệ thống quy mô lớn có cấu trúc M:N, luồng “client mô tả thứ mình cần → server tổ hợp → domain service xử lý” về lâu dài dễ quản lý hơn nhiều
Khi kết hợp với observability tốt, nó trở thành một nền tảng rất mạnh cho truy cập dữ liệu
Một điểm nữa là resolver tái sử dụng được và federation khá dễ. Với REST thì chuyện này khá phiền
Về tiến hóa schema, Protobuf cũng xử lý tốt
Lợi thế thực sự của GraphQL là có thể ghép dữ liệu UI từ các mảnh nhỏ
Như trong video này, nếu tận dụng fragment colocation thì khi chỉnh sửa component con cũng không ảnh hưởng đến phần khác
Vì query được tự động tạo dựa trên fragment, nên khi xóa field cũng giảm nguy cơ làm hỏng component khác
Nếu quy mô nhỏ hoặc tốc độ phát triển không quá quan trọng, GraphQL có thể trông như một khoản đầu tư quá mức
Colocation thực sự là một khái niệm mang tính cách mạng, nhưng Apollo gần như không nhắc đến điều này
Tài liệu của Relay vẫn còn thiếu, nhưng khái niệm Entrypoint thì rất tuyệt
Nhưng cách triển khai của graphql-codegen lại có độ tương thích plugin kém, nên chúng tôi phải tự viết plugin
Toàn bộ hệ sinh thái thiếu tính nhất quán
Nói overfetching là vấn đề cốt lõi của GraphQL thì hơi phóng đại
Tôi cho rằng GraphQL là công cụ xử lý impedance mismatch mà ORM giải quyết, nhưng ở cấp độ client
Dùng nó mà không có tooling dựa trên compiler như Relay là một anti-pattern
Dạo này AI tự sinh data layer, nên có vẻ nhu cầu với GraphQL đang giảm đi
Chỉ cần chọn một field không tồn tại là nó tự sinh giúp, nên trải nghiệm phát triển (DevEx) rất tốt
Sự chậm chạp của web hiện đại phần lớn đến từ truyền dữ liệu dư thừa. Trên thực tế, nhiều ứng dụng hoạt động với hiệu suất dưới 0.5%
Tôi đã dùng GraphQL từ năm 2016
Về bản chất, GraphQL là một đặc tả RPC. Nó được triển khai dưới dạng ánh xạ “Action(Args) → ResultType” của server
Thay vì nhiều endpoint như REST, GraphQL hoạt động qua một endpoint
/queryduy nhất thông qua resolver mapRốt cuộc nó cũng là một cấu trúc mà input và output được định nghĩa bằng kiểu giống như OpenAPI hay gRPC
Apollo có khá hơn một chút sau khi thêm fragment masking, nhưng tư duy lấy Relay làm trung tâm vẫn rất quan trọng
rồi backend chuyển nó thành một truy vấn SQL duy nhất để xử lý
Resolver chỉ nên dùng ngoại lệ cho dữ liệu không thể lấy trực tiếp từ DB
Trước đây khi dẫn dắt một team, FE muốn GraphQL nên tôi đã đưa nó vào
Kết quả là mỗi trang đều lấy mọi dữ liệu bằng một query khổng lồ, và khi chỉnh sửa thì lại gửi toàn bộ JSON blob lên lần nữa
Ứng dụng vẫn chạy được, nhưng rồi công ty pivot nên đoạn code đó cũng biến mất
Trên thực tế, có vẻ nhiều dự án GraphQL cũng kết thúc theo kiểu triển khai cho có hình thức như vậy
Luồng xác thực (auth) của GraphQL là một trong những bài toán khó nhất
Vì resolver được gọi trong nhiều ngữ cảnh khác nhau, nên phải tính đến mọi trường hợp
Độ phức tạp và chi phí tư duy quá lớn, cuối cùng lại phải khóa field
Tôi từng tự làm graphql-autharoo, nhưng vẫn chưa đủ
Phần lớn chức năng có thể được triển khai đơn giản hơn nếu không dùng GraphQL
Phải thêm quyền chi tiết cho từng resource GraphQL, nên cho đến khi toàn bộ resource được cập nhật xong thì query vẫn không chạy được
Nó nặng nề đến mức người ta phải nói rằng “thà làm lại bằng REST còn hơn”
Tôi nghĩ tốt hơn là dùng một server framework hoàn chỉnh thay vì tự ghép mọi thứ lại
GraphQL nhìn bề ngoài thì rất hay, nhưng trên thực tế là một công nghệ càng về lâu dài càng khó bảo trì
Cũng như nhiều công nghệ khác, nó rút ra bài học rằng cuối cùng thì bánh xe vẫn chỉ là bánh xe
Điểm mạnh của GraphQL là chỉ cần thêm field vào schema thì mọi client đều có thể query ngay lập tức
Những yêu cầu từ FE kiểu “hãy thêm field này nữa” biến mất, nên việc cộng tác trở nên đơn giản hơn
Ngoài ra cũng dễ tạo schema snapshot để phát hiện thay đổi trong integration test
Tôi thấy nó nhất quán hơn REST, nhưng đó chỉ là trải nghiệm cá nhân
Vì có thể request dây chuyền từ A đến E, nên dễ dẫn đến suy giảm hiệu năng hoặc sự gắn chặt giữa frontend và cấu trúc dữ liệu
React giờ cũng lấy SSR dựa trên framework và server component làm mặc định
Cuối cùng thì xu hướng đang đi theo hướng TypeScript hợp nhất client và server
Tôi tò mò GraphQL ngăn vấn đề tải DB hoặc query kém hiệu quả như thế nào
Chẳng phải query ác ý có thể làm nổ tung trạng thái nội bộ sao?
Tôi đang thử GraphQL vì REST quá nhàm chán
Tôi thích điểm là server và client có thể đồng thuận request·response ở thời điểm compile
Giá mà các blog đừng chỉ nói “cái này dở” mà còn đưa ra cả công nghệ thay thế
Cả hai công nghệ đều giải quyết rất tốt bài toán hợp đồng client-server