ADR-0003: Thêm lớp GraphQL additive tại api-gateway (song song với REST)
- Status: Accepted
- Date: 2026-07-24
- Supersedes: —
- Superseded by: —
Bối cảnh
api-gateway vốn chỉ expose REST (proxy sang các service nội bộ). Nhu cầu
phát sinh: FE cần query linh hoạt dữ liệu tổng hợp từ nhiều service (VD:
field + reviews + availability + owner dashboard) mà không muốn gọi nhiều
REST endpoint rồi tự ghép ở client, dẫn tới over-fetch/under-fetch và nhiều
round-trip. Đồng thời không muốn phá vỡ các client REST hiện có.
Quyết định
Thêm một lớp GraphQL bổ sung (additive) tại api-gateway, expose duy
nhất tại POST /graphql, dùng @nestjs/graphql + @apollo/server:
- REST endpoint giữ nguyên, không bị thay thế — GraphQL "sits alongside"
(theo
docs/guides/GRAPHQL_API_GUIDE_EN.md) - Dùng DataLoader (
apps/api-gateway/src/graphql/loaders/create-loaders.ts) để batch/cache request tới các service nội bộ trong 1 request GraphQL, tránh N+1 - Auth tái dùng Keycloak JWT hiện có (
Authorization: Bearer <keycloak-jwt>) graphql-depth-limitđể chặn query quá sâu (bảo vệ khỏi truy vấn độc hại)- Custom throttler riêng cho GraphQL (
security/gql-throttler.guard.ts) - Tracing riêng cho resolver (
graphql/tracing/resolver-tracing.plugin.ts) để giữ observability nhất quán với REST (xem ADR-0004) - Docs tự sinh dạng Swagger-style tại
GET /graphql/docs(spectaql), chỉ bật non-production
Lựa chọn khác đã cân nhắc
- Thay REST hoàn toàn bằng GraphQL: chi phí migrate toàn bộ client hiện tại quá lớn, rủi ro breaking change cao — loại.
- BFF (Backend-for-Frontend) riêng biệt ngoài api-gateway: thêm 1 service mới cần vận hành, trong khi api-gateway đã có sẵn context auth/proxy tới mọi service — loại vì overhead không cần thiết ở quy mô hiện tại.
- tRPC: phù hợp hơn khi FE/BE cùng ngôn ngữ và cùng monorepo type-sharing chặt, nhưng hệ sinh thái schema-first/tooling (playground, depth-limit, codegen cho nhiều loại client) của GraphQL phù hợp hơn với nhu cầu FE đa dạng — loại.
Hệ quả
- Được: FE lấy đúng dữ liệu cần trong 1 request, giảm over/under-fetch; DataLoader giảm N+1 khi resolver gọi nhiều service; REST không bị ảnh hưởng (rollout an toàn, có thể rollback GraphQL riêng nếu cần).
- Đánh đổi: thêm bề mặt tấn công mới cần rate-limit/depth-limit riêng; thêm một tầng resolver cần maintain song song REST controller cho cùng domain; team cần hiểu thêm GraphQL schema design + DataLoader caching semantics.