GraphQL Federation — Gộp API Microservices thành một Supergraph thống nhất
Posted on: 4/22/2026 2:12:26 AM
Table of contents
- 1. Vấn đề: API Gateway truyền thống không scale theo tổ chức
- 2. GraphQL Federation là gì?
- 3. Cách Federation compose schema: Directives quan trọng
- 4. Apollo Router — Bộ não của Supergraph
- 5. Thiết kế Subgraph đúng cách
- 6. Schema Composition và CI/CD
- 7. Query Planning và Execution — Hiểu sâu cách Router hoạt động
- 8. Bảo mật và Authorization trong Federation
- 9. Performance Optimization
- 10. Federation trong .NET với Hot Chocolate
- 11. Monitoring và Observability
- 12. Khi nào KHÔNG nên dùng Federation?
- 13. Alternatives so sánh
- Kết luận
Trong thế giới microservices, mỗi team sở hữu một service riêng biệt với schema API của riêng mình. Nhưng phía client — web app, mobile app — chỉ muốn một endpoint duy nhất để query tất cả dữ liệu cần thiết. Đây chính là bài toán mà GraphQL Federation giải quyết: cho phép nhiều subgraph (mỗi subgraph là một microservice) được compose thành một supergraph thống nhất, với một gateway ở giữa điều phối mọi thứ.
1. Vấn đề: API Gateway truyền thống không scale theo tổ chức
Khi hệ thống microservices mở rộng lên hàng chục, hàng trăm service, mô hình API Gateway truyền thống (REST) gặp phải nhiều vấn đề nghiêm trọng:
- Over-fetching / Under-fetching: Client phải gọi nhiều endpoint riêng lẻ, nhận về dữ liệu thừa hoặc thiếu, rồi tự ghép nối.
- Gateway monolith: Một team trung tâm phải maintain toàn bộ routing logic, trở thành bottleneck của tổ chức.
- Schema coupling: Khi service A thêm field mới, gateway phải deploy lại — dù service B, C không liên quan.
- API versioning hell: REST buộc phải duy trì v1, v2, v3... song song, tăng complexity theo thời gian.
Tại sao không dùng GraphQL thường?
GraphQL (monolithic) giải quyết over-fetching nhưng lại tạo ra single schema monolith — một file schema khổng lồ mà mọi team phải commit vào cùng repo. Federation giải phóng constraint đó bằng cách cho mỗi team sở hữu subgraph riêng.
2. GraphQL Federation là gì?
GraphQL Federation là một kiến trúc cho phép bạn chia GraphQL schema thành nhiều subgraph độc lập, mỗi subgraph do một team/service sở hữu. Một router (gateway) sẽ tự động compose (gộp) tất cả subgraph thành một supergraph duy nhất mà client tương tác.
graph TD
Client["🖥️ Client App"] --> Router["Apollo Router
(Supergraph Gateway)"]
Router --> S1["Subgraph: Users
Team A"]
Router --> S2["Subgraph: Products
Team B"]
Router --> S3["Subgraph: Orders
Team C"]
Router --> S4["Subgraph: Reviews
Team D"]
style Client fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style Router fill:#e94560,stroke:#fff,color:#fff
style S1 fill:#2c3e50,stroke:#fff,color:#fff
style S2 fill:#2c3e50,stroke:#fff,color:#fff
style S3 fill:#2c3e50,stroke:#fff,color:#fff
style S4 fill:#2c3e50,stroke:#fff,color:#fff
2.1. Ba khái niệm cốt lõi
| Khái niệm | Mô tả | Ví dụ |
|---|---|---|
| Subgraph | Một GraphQL service độc lập, định nghĩa một phần schema. Mỗi team sở hữu ≥1 subgraph. | Users subgraph định nghĩa type User |
| Supergraph | Schema tổng hợp, được compose tự động từ tất cả subgraph. Client chỉ thấy supergraph. | Query { user { orders { items } } } xuyên suốt 3 subgraph |
| Router | Gateway nhận query từ client, phân tích query plan, gọi đúng subgraph cần thiết, rồi merge kết quả. | Apollo Router, Cosmo Router, Grafbase Gateway |
3. Cách Federation compose schema: Directives quan trọng
Federation sử dụng một số directive đặc biệt để các subgraph "nói chuyện" với nhau qua router, mà không cần biết nhau trực tiếp.
3.1. @key — Định danh entity liên kết
Directive @key đánh dấu một type là entity — có thể được resolve bởi nhiều subgraph khác nhau dựa trên primary key.
# Subgraph: Users (Team A sở hữu)
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
avatar: String
}
type Query {
user(id: ID!): User
users: [User!]!
}
# Subgraph: Reviews (Team D sở hữu)
# Team D "mở rộng" type User mà KHÔNG cần sửa Users subgraph
type User @key(fields: "id") {
id: ID!
reviews: [Review!]! # Team D thêm field này
}
type Review @key(fields: "id") {
id: ID!
rating: Int!
comment: String!
author: User!
}
Entity Resolution
Khi router nhận query { user(id: "1") { name reviews { rating } } }, nó sẽ: (1) gọi Users subgraph lấy name, (2) gọi Reviews subgraph với __resolveReference({ id: "1" }) để lấy reviews, rồi (3) merge kết quả. Client không biết data đến từ 2 service khác nhau.
3.2. @external, @requires, @provides — Cross-subgraph dependencies
# Subgraph: Products
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
weight: Float!
}
# Subgraph: Shipping
type Product @key(fields: "id") {
id: ID!
weight: Float! @external # Khai báo field từ subgraph khác
shippingCost: Float! @requires(fields: "weight") # Cần weight để tính
}
Khi client query shippingCost, router tự động fetch weight từ Products subgraph trước, rồi truyền cho Shipping subgraph để tính phí.
3.3. @shareable — Nhiều subgraph cùng resolve một field
# Cả Users và Auth subgraph đều có thể resolve email
type User @key(fields: "id") {
id: ID!
email: String! @shareable
}
4. Apollo Router — Bộ não của Supergraph
Apollo Router (viết bằng Rust) là thành phần trung tâm trong kiến trúc Federation. Nó thay thế Apollo Gateway (Node.js) với hiệu năng vượt trội.
graph LR
Q["Client Query"] --> QP["Query Planner
(phân tích cần subgraph nào)"]
QP --> EE["Execution Engine
(gọi song song các subgraph)"]
EE --> S1["Subgraph A"]
EE --> S2["Subgraph B"]
EE --> S3["Subgraph C"]
S1 --> MR["Response Merger
(ghép kết quả)"]
S2 --> MR
S3 --> MR
MR --> R["Response → Client"]
style Q fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style QP fill:#e94560,stroke:#fff,color:#fff
style EE fill:#e94560,stroke:#fff,color:#fff
style MR fill:#e94560,stroke:#fff,color:#fff
style R fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style S1 fill:#2c3e50,stroke:#fff,color:#fff
style S2 fill:#2c3e50,stroke:#fff,color:#fff
style S3 fill:#2c3e50,stroke:#fff,color:#fff
| Tiêu chí | Apollo Gateway (Node.js) | Apollo Router (Rust) |
|---|---|---|
| RAM @ 10K QPS | ~800 MB | ~120 MB |
| Query planning overhead | 3–5 ms | ~0.5 ms |
| Cold start | Vài giây (Node.js boot) | <100 ms |
| Plugin system | JavaScript middleware | Rhai scripting + Rust native |
| Ngôn ngữ | JavaScript/TypeScript | Rust |
| Trạng thái | Legacy (không khuyến khích) | Production-ready, actively developed |
4.1. Cấu hình Router cơ bản
# router.yaml
supergraph:
listen: 0.0.0.0:4000
introspection: true
cors:
origins:
- https://app.example.com
headers:
all:
request:
- propagate:
named: "Authorization"
- propagate:
named: "X-Request-Id"
telemetry:
exporters:
tracing:
otlp:
enabled: true
endpoint: http://otel-collector:4317
limits:
max_depth: 15
max_height: 200
coprocessor:
url: http://auth-service:8080
router:
request:
headers: true
5. Thiết kế Subgraph đúng cách
5.1. Nguyên tắc Separation of Concerns
Mỗi subgraph nên đại diện cho một bounded context trong domain — tương tự Domain-Driven Design.
graph TD
subgraph "E-commerce Supergraph"
UG["Users Subgraph
Profiles, Auth, Preferences"]
PG["Products Subgraph
Catalog, Inventory, Pricing"]
OG["Orders Subgraph
Cart, Checkout, Fulfillment"]
RG["Reviews Subgraph
Ratings, Comments, Moderation"]
SG["Search Subgraph
Full-text, Filters, Suggestions"]
end
UG -.->|"@key: User.id"| RG
PG -.->|"@key: Product.id"| OG
PG -.->|"@key: Product.id"| RG
UG -.->|"@key: User.id"| OG
PG -.->|"@key: Product.id"| SG
style UG fill:#e94560,stroke:#fff,color:#fff
style PG fill:#2c3e50,stroke:#fff,color:#fff
style OG fill:#4CAF50,stroke:#fff,color:#fff
style RG fill:#ff9800,stroke:#fff,color:#fff
style SG fill:#9C27B0,stroke:#fff,color:#fff
5.2. Entity ownership vs. contribution
Quy tắc vàng
Chỉ một subgraph sở hữu (own) một entity — nó định nghĩa các field "core" và resolver chính. Các subgraph khác chỉ contribute (đóng góp) thêm field mới cho entity đó qua directive @key. Ví dụ: Users subgraph owns User.name, User.email; Reviews subgraph contributes User.reviews.
5.3. Tránh circular dependencies
# ❌ TRÁNH: Circular reference sâu
# Users subgraph
type User @key(fields: "id") {
id: ID!
orders: [Order!]! # User → Order
}
# Orders subgraph
type Order @key(fields: "id") {
id: ID!
buyer: User! # Order → User
items: [OrderItem!]!
}
type OrderItem {
product: Product! # OrderItem → Product
}
# Products subgraph
type Product @key(fields: "id") {
id: ID!
reviews: [Review!]! # Product → Review
}
# Reviews subgraph
type Review {
author: User! # Review → User → vòng lặp!
# ✅ TỐT HƠN: Giữ reference một chiều, dùng ID khi cần
# Reviews subgraph
type Review @key(fields: "id") {
id: ID!
rating: Int!
comment: String!
authorId: ID! # Chỉ trả ID, client tự resolve nếu cần
author: User! # Hoặc vẫn resolve nhưng shallow (chỉ @key fields)
}
6. Schema Composition và CI/CD
Trong production, schema composition diễn ra trong CI/CD pipeline — không phải runtime. Điều này đảm bảo mọi breaking change được phát hiện trước khi deploy.
graph LR
Dev["Developer pushes
subgraph change"] --> CI["CI Pipeline"]
CI --> Check["rover subgraph check
(composition + breaking change detection)"]
Check -->|Pass| Pub["rover subgraph publish
(update schema registry)"]
Check -->|Fail| Fix["Fix schema
incompatibility"]
Pub --> Router["Router hot-reloads
new supergraph schema"]
Fix --> Dev
style Dev fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style CI fill:#2c3e50,stroke:#fff,color:#fff
style Check fill:#e94560,stroke:#fff,color:#fff
style Pub fill:#4CAF50,stroke:#fff,color:#fff
style Fix fill:#ff9800,stroke:#fff,color:#fff
style Router fill:#2c3e50,stroke:#fff,color:#fff
6.1. Rover CLI — Công cụ quản lý schema
# Kiểm tra subgraph change có tương thích không
rover subgraph check my-graph@production \
--name users \
--schema ./users/schema.graphql
# Publish subgraph lên registry
rover subgraph publish my-graph@production \
--name users \
--schema ./users/schema.graphql \
--routing-url http://users-service:4001/graphql
# Fetch supergraph schema hiện tại
rover supergraph fetch my-graph@production
6.2. Breaking change detection
| Loại thay đổi | Severity | Ví dụ |
|---|---|---|
| Xóa field đang được query | 🔴 Breaking | Xóa User.email khi 50 operation đang dùng |
| Đổi type của field | 🔴 Breaking | price: Float! → price: String! |
| Thêm required argument | 🟡 Potentially breaking | users(limit: Int!) — client cũ thiếu argument |
| Thêm field mới | 🟢 Safe | Thêm User.lastLoginAt |
| Deprecate field | 🟢 Safe | email: String! @deprecated(reason: "Use contactEmail") |
7. Query Planning và Execution — Hiểu sâu cách Router hoạt động
Khi router nhận một query, nó thực hiện query planning — phân tích query thành execution plan gọi đúng subgraph cần thiết, theo đúng thứ tự dependency.
# Client gửi query này:
query GetUserDashboard($userId: ID!) {
user(id: $userId) {
name # → Users subgraph
email # → Users subgraph
orders(last: 5) { # → Orders subgraph
id
total
items {
product {
name # → Products subgraph
price # → Products subgraph
}
}
}
reviews { # → Reviews subgraph
rating
comment
}
}
}
Query Plan của Router
Router tạo execution plan: Fetch User (parallel) → Fetch Orders + Reviews → Fetch Products. Các subgraph call không phụ thuộc nhau sẽ chạy song song. Router cũng cache query plan (plan caching) nên query giống nhau lần sau chỉ mất ~0.01ms thay vì ~0.5ms.
8. Bảo mật và Authorization trong Federation
Authorization trong federated graph phức tạp hơn monolith vì data đến từ nhiều service. Có 2 pattern phổ biến:
8.1. Router-level authentication + Subgraph-level authorization
# router.yaml — xác thực JWT tại router
authentication:
router:
jwt:
jwks:
url: https://auth.example.com/.well-known/jwks.json
header_name: Authorization
header_value_prefix: Bearer
# Subgraph: Orders — phân quyền tại subgraph
type Order @key(fields: "id") @authenticated {
id: ID!
total: Float!
buyerId: ID!
items: [OrderItem!]! @requiresScopes(scopes: [["order:read"]])
internalNotes: String @requiresScopes(scopes: [["admin"]])
}
8.2. Directive @policy cho business rules
type User @key(fields: "id") {
id: ID!
name: String!
email: String! @policy(policies: [["self_or_admin"]])
salary: Float! @policy(policies: [["hr_department"]])
}
9. Performance Optimization
9.1. DataLoader pattern — Giải quyết N+1 query
Khi query danh sách orders có buyer, nếu không dùng DataLoader, Users subgraph sẽ bị gọi N lần (mỗi order 1 lần). DataLoader batch các request thành 1 call duy nhất.
// Users subgraph — Reference resolver với DataLoader
import DataLoader from 'dataloader';
const userLoader = new DataLoader<string, User>(async (ids) => {
const users = await db.users.findMany({
where: { id: { in: ids as string[] } }
});
const userMap = new Map(users.map(u => [u.id, u]));
return ids.map(id => userMap.get(id)!);
});
const resolvers = {
User: {
__resolveReference(ref: { id: string }) {
return userLoader.load(ref.id);
}
}
};
9.2. @defer — Streaming response
# Client nhận name/email ngay, reviews load sau (streaming)
query {
user(id: "1") {
name
email
... @defer(label: "reviews") {
reviews {
rating
comment
}
}
}
}
9.3. Persisted Queries
# router.yaml
persisted_queries:
enabled: true
safelist:
enabled: true # Chỉ cho phép query đã đăng ký
require_id: true # Client gửi hash thay vì full query text
Lưu ý hiệu năng
Tránh thiết kế subgraph có quá nhiều entity reference qua lại — mỗi hop giữa các subgraph thêm ~2-5ms network latency. Nếu một query phải qua 4-5 subgraph lần lượt (sequential), tổng latency có thể lên 20-30ms chỉ riêng overhead. Thiết kế sao cho phần lớn query chỉ cần 1-2 hop.
10. Federation trong .NET với Hot Chocolate
Nếu bạn dùng .NET, Hot Chocolate (ChilliCream) hỗ trợ Federation 2 đầy đủ:
// Program.cs — .NET 10 subgraph
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddGraphQLServer()
.AddApolloFederationV2()
.AddQueryType<Query>()
.AddType<UserType>()
.AddType<ReviewType>()
.RegisterService<IReviewRepository>();
var app = builder.Build();
app.MapGraphQL();
app.Run();
// UserType.cs — Entity type với @key
[Key("id")]
public class UserType : ObjectType<User>
{
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
{
descriptor.Field(u => u.Id).Type<NonNullType<IdType>>();
descriptor.Field("reviews")
.ResolveWith<UserResolvers>(r => r.GetReviews(default!, default!));
}
}
// Reference resolver — router gọi khi cần resolve User entity
[ReferenceResolver]
public static async Task<User?> GetUserById(
[ID] int id,
IReviewRepository repo)
{
return await repo.GetUserReferenceAsync(id);
}
11. Monitoring và Observability
Trong federated architecture, observability đặc biệt quan trọng vì một query có thể đi qua nhiều subgraph:
11.1. Distributed tracing
# router.yaml — OpenTelemetry tracing
telemetry:
exporters:
tracing:
otlp:
enabled: true
endpoint: http://otel-collector:4317
protocol: grpc
instrumentation:
spans:
router:
attributes:
graphql.document: true
subgraph:
attributes:
subgraph.name: true
graphql.operation.name: true
11.2. Các metric cần monitor
| Metric | Mô tả | Threshold cảnh báo |
|---|---|---|
apollo.router.query_planning.duration |
Thời gian tạo query plan | >5ms (có thể cần plan cache warming) |
apollo.router.http.request.duration |
Tổng thời gian request end-to-end | p99 >500ms |
subgraph.request.duration per subgraph |
Latency từng subgraph | Subgraph nào chậm nhất = bottleneck |
apollo.router.cache.hit_rate |
Tỷ lệ query plan cache hit | <90% là đáng lo |
graphql.error.count |
Số lỗi GraphQL (partial response) | Tăng đột biến = subgraph down |
12. Khi nào KHÔNG nên dùng Federation?
Federation không phải silver bullet
- Team nhỏ (<3 backend devs): Overhead quản lý nhiều subgraph, router, schema registry vượt quá lợi ích. Dùng monolithic GraphQL là đủ.
- Schema ít thay đổi: Nếu API schema ổn định, REST + OpenAPI vẫn đơn giản hơn nhiều.
- Latency-critical (sub-ms): Mỗi hop qua router thêm overhead — high-frequency trading hoặc game server nên dùng gRPC trực tiếp.
- Chưa có domain boundaries rõ ràng: Federation ép bạn phải phân chia subgraph — nếu domain model chưa mature, bạn sẽ phải refactor liên tục.
13. Alternatives so sánh
| Giải pháp | Ưu điểm | Nhược điểm | Phù hợp khi |
|---|---|---|---|
| GraphQL Federation | Unified schema, team autonomy, type-safe | Complexity, learning curve, router overhead | Large org, nhiều team, domain phức tạp |
| REST + API Gateway | Đơn giản, tooling mature, caching dễ | Over-fetching, nhiều endpoint, versioning | CRUD apps, public API, team nhỏ |
| gRPC + Envoy | Hiệu năng cao, strong typing, streaming | Không friendly cho frontend, binary protocol | Service-to-service, latency-critical |
| Schema Stitching | Merge schema đơn giản hơn | Không scale theo tổ chức, fragile | 2-3 service, prototype nhanh |
Kết luận
GraphQL Federation là giải pháp kiến trúc mạnh mẽ cho bài toán "làm sao để nhiều team cùng xây dựng một API thống nhất mà không tạo bottleneck". Với Apollo Router (Rust), schema composition trong CI/CD, và hệ sinh thái ngày càng đa dạng (Hot Chocolate cho .NET, federation-jvm cho Java/Kotlin), Federation không còn là experiment mà đã trở thành production-proven pattern cho các tổ chức quy mô lớn.
Tuy nhiên, hãy nhớ: Federation giải quyết vấn đề tổ chức (organizational scaling) trước vấn đề kỹ thuật. Nếu team bạn nhỏ và domain đơn giản, một monolithic GraphQL server vẫn là lựa chọn tốt hơn.
Nguồn tham khảo:
Bảo mật Container và Supply Chain Security 2026 — SBOM, Cosign, SLSA, Trivy cho DevSecOps
Multi-Region Deployment 2026 — Kiến trúc đa vùng cho hệ thống không được phép sập
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.