GraphQL Federation — Xây dựng API Gateway thống nhất cho Microservices
Posted on: 4/25/2026 6:12:31 AM
Table of contents
- 1. Vấn đề — Khi REST API Gateway không đủ
- 2. Cac khai niem cot loi
- 3. Federation v2 — Nhung cai tien quan trong
- 4. Chon Router — Apollo vs Cosmo vs HotChocolate
- 5. Trien khai tren .NET voi HotChocolate
- 6. Hieu nang — Federation co cham hon REST?
- 7. Caching — Thach thuc lon nhat cua GraphQL
- 8. Apollo Connectors — Tich hop REST vao Federation
- 9. Best Practices cho Production
- 10. Khi nao nen dung Federation?
- 11. So sanh tong quan cac approach
- 12. Ket luan
Khi hệ thống microservices phát triển đến hàng chục, hàng trăm service — câu hỏi lớn nhất không còn là "dùng REST hay gRPC?" mà là: làm sao để frontend chỉ gọi một endpoint duy nhất mà vẫn truy cập được data từ tất cả services? REST API Gateway giải quyết được routing, nhưng không giải quyết được bài toán data composition — khi một trang cần dữ liệu từ 5-10 services khác nhau, frontend phải gọi nhiều API rồi tự ghép. GraphQL Federation sinh ra để giải quyết đúng vấn đề này: mỗi team sở hữu một subgraph, và router tự động compose thành một unified API duy nhất. Bài viết này phân tích sâu kiến trúc, triển khai trên .NET với HotChocolate, và các bài học production thực tế.
1. Vấn đề — Khi REST API Gateway không đủ
Hãy tưởng tượng một trang product detail trên e-commerce: cần thông tin sản phẩm (Product Service), giá và tồn kho (Inventory Service), đánh giá (Review Service), gợi ý sản phẩm (Recommendation Service), và thông tin người bán (Seller Service). Với REST, frontend phải:
sequenceDiagram
participant FE as Frontend
participant GW as API Gateway
participant PS as Product Service
participant IS as Inventory Service
participant RS as Review Service
participant RC as Recommendation
participant SS as Seller Service
FE->>GW: GET /products/123
GW->>PS: GET /products/123
PS-->>GW: Product data
GW-->>FE: Product data
FE->>GW: GET /inventory/product/123
GW->>IS: GET /inventory/product/123
IS-->>GW: Stock info
GW-->>FE: Stock info
FE->>GW: GET /reviews?productId=123
GW->>RS: GET /reviews?productId=123
RS-->>GW: Reviews
GW-->>FE: Reviews
FE->>GW: GET /recommendations/123
GW->>RC: GET /recommendations/123
RC-->>GW: Suggestions
GW-->>FE: Suggestions
FE->>GW: GET /sellers/456
GW->>SS: GET /sellers/456
SS-->>GW: Seller info
GW-->>FE: Seller info
Hinh 1: REST pattern — Frontend phai goi 5 API rieng le de render 1 trang
5 round-trips tu frontend den gateway, moi request phai doi request truoc hoan thanh (waterfall). Voi GraphQL Federation:
sequenceDiagram
participant FE as Frontend
participant RT as GraphQL Router
participant PS as Product Subgraph
participant IS as Inventory Subgraph
participant RS as Review Subgraph
participant RC as Recommendation Subgraph
participant SS as Seller Subgraph
FE->>RT: query { product(id:123) { name, price, stock, reviews { ... }, recommendations { ... }, seller { ... } } }
RT->>PS: Resolve product
RT->>IS: Resolve stock
RT->>RS: Resolve reviews
RT->>RC: Resolve recommendations
PS-->>RT: Product data
IS-->>RT: Stock info
RS-->>RT: Reviews
RC-->>RT: Suggestions
RT->>SS: Resolve seller (from product.sellerId)
SS-->>RT: Seller info
RT-->>FE: Unified response
Hinh 2: GraphQL Federation — 1 query duy nhat, Router tu dong fan-out va compose
2. Cac khai niem cot loi
GraphQL Federation xoay quanh 3 thanh phan chinh:
graph TB
subgraph "Team A: Product"
SG1["Product Subgraph
type Product @key(fields: id)"]
end
subgraph "Team B: Inventory"
SG2["Inventory Subgraph
extend type Product
stock: Int"]
end
subgraph "Team C: Reviews"
SG3["Review Subgraph
extend type Product
reviews: [Review]"]
end
subgraph "Supergraph"
ROUTER["GraphQL Router
(Apollo Router / Cosmo / HotChocolate Fusion)"]
end
SG1 --> ROUTER
SG2 --> ROUTER
SG3 --> ROUTER
CLIENT["Frontend Client"] --> ROUTER
style ROUTER fill:#e94560,stroke:#fff,color:#fff
style SG1 fill:#4CAF50,stroke:#fff,color:#fff
style SG2 fill:#2196F3,stroke:#fff,color:#fff
style SG3 fill:#ff9800,stroke:#fff,color:#fff
style CLIENT fill:#2c3e50,stroke:#fff,color:#fff
Hinh 3: Kien truc GraphQL Federation — moi team so huu 1 subgraph
| Khai niem | Mo ta | Vi du |
|---|---|---|
| Subgraph | GraphQL service do 1 team so huu. Dinh nghia cac type va field ma team do chiu trach nhiem. | Product Subgraph dinh nghia type Product { id, name, price } |
| Supergraph | Schema thong nhat duoc compose tu tat ca subgraphs. Client chi thay supergraph, khong biet co bao nhieu subgraph phia sau. | type Product { id, name, price, stock, reviews } |
| Router | Runtime nhan query tu client, phan tich query plan, fan-out den cac subgraph lien quan, va compose ket qua tra ve. | Apollo Router (Rust), Cosmo Router (Go), HotChocolate Fusion (.NET) |
| @key | Directive danh dau entity — cho phep cac subgraph khac reference va extend type nay. | type Product @key(fields: "id") |
| @external | Field duoc dinh nghia o subgraph khac, chi reference tai day. | extend type Product { id: ID! @external } |
| @requires | Field can data tu subgraph khac de resolve. | shippingCost: Float @requires(fields: "weight") |
3. Federation v2 — Nhung cai tien quan trong
Federation v2 (phien ban hien tai: v2.11) mang den nhieu cai tien so voi v1:
| Tinh nang | Federation v1 | Federation v2 |
|---|---|---|
| Shared types | Moi type chi duoc 1 subgraph dinh nghia | @shareable — nhieu subgraph co the dinh nghia cung 1 field |
| Schema evolution | Breaking changes kho kiem soat | @override — migrate field giua cac subgraph an toan |
| Input types | Khong chia se duoc | @inaccessible — an field khoi supergraph ma khong xoa |
| Composition | Runtime composition tai gateway | Build-time composition — loi duoc bat truoc khi deploy |
| Error messages | Mo ho, kho debug | Composition hints chi tiet, chi ro vi tri loi |
| Progressive migration | Khong ho tro | @override(label: "percent(50)") — canary migration |
# Product Subgraph — Team Product so huu
# schema.graphql
extend schema @link(
url: "https://specs.apollo.dev/federation/v2.11"
import: ["@key", "@shareable", "@override"]
)
type Product @key(fields: "id") {
id: ID!
name: String!
description: String
price: Float!
weight: Float
sellerId: ID!
seller: Seller
createdAt: DateTime!
}
type Query {
product(id: ID!): Product
products(first: Int = 10, after: String): ProductConnection!
}
# Inventory Subgraph — Team Inventory so huu
# schema.graphql
extend schema @link(
url: "https://specs.apollo.dev/federation/v2.11"
import: ["@key", "@external", "@requires"]
)
type Product @key(fields: "id") {
id: ID!
stock: Int!
warehouse: String!
weight: Float @external
shippingCost: Float @requires(fields: "weight")
}
# Review Subgraph — Team Review so huu
# schema.graphql
extend schema @link(
url: "https://specs.apollo.dev/federation/v2.11"
import: ["@key"]
)
type Product @key(fields: "id") {
id: ID!
reviews: [Review!]!
averageRating: Float
reviewCount: Int!
}
type Review {
id: ID!
author: User!
rating: Int!
comment: String
createdAt: DateTime!
}
Entity Resolution — Tim hieu cach Router ghep du lieu
Khi client query product(id: "123") { name, stock, reviews }, Router thuc hien:
Buoc 1: Gui query den Product Subgraph de lay name va id.
Buoc 2: Dung id tu buoc 1, gui song song den Inventory Subgraph (lay stock) va Review Subgraph (lay reviews).
Buoc 3: Merge ket qua thanh 1 JSON response duy nhat.
Toan bo qua trinh nay trong suot voi client — client chi thay 1 schema thong nhat, khong biet data den tu dau.
4. Chon Router — Apollo vs Cosmo vs HotChocolate
Router la thanh phan quan trong nhat trong Federation. Lua chon phu hop anh huong truc tiep den hieu nang va developer experience:
| Tieu chi | Apollo Router | Cosmo Router | HotChocolate Fusion |
|---|---|---|---|
| Ngon ngu | Rust | Go | C# (.NET) |
| License | Elastic v2 (source-available) | Apache 2.0 (open-source) | MIT (open-source) |
| Hieu nang | Rat cao (Rust runtime) | Cao (Go runtime) | Cao (Kestrel + .NET 10) |
| Federation spec | v2.11 (tac gia spec) | v2.x compatible | v2.x + Fusion rieng |
| Connectors | REST Connectors (GraphOS) | Khong | Schema Stitching legacy |
| Managed platform | Apollo GraphOS (tra phi) | Cosmo Cloud (co free tier) | Tu host |
| Phu hop | Team da ngon ngu, enterprise | Team can open-source thuc su | Team .NET, tich hop ASP.NET Core |
5. Trien khai tren .NET voi HotChocolate
Voi team .NET, HotChocolate v14 la lua chon production-grade duy nhat. Ho tro ca Apollo Federation v2 spec va he sinh thai Fusion rieng cua ChilliCream:
5.1. Tao Product Subgraph
// ProductSubgraph/Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddGraphQLServer()
.AddApolloFederation() // Enable Federation v2
.AddQueryType<ProductQuery>()
.AddType<ProductType>()
.AddFiltering()
.AddSorting()
.RegisterDbContext<ProductDbContext>();
var app = builder.Build();
app.MapGraphQL();
app.Run();
// ProductSubgraph/Types/ProductType.cs
[Key("id")] // Federation @key directive
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = default!;
public string? Description { get; set; }
public decimal Price { get; set; }
public float? Weight { get; set; }
public int SellerId { get; set; }
public DateTime CreatedAt { get; set; }
}
public class ProductQuery
{
[UseDbContext(typeof(ProductDbContext))]
[UseFiltering]
[UseSorting]
public IQueryable<Product> GetProducts(
[ScopedService] ProductDbContext context)
=> context.Products;
public async Task<Product?> GetProductById(
int id,
ProductByIdDataLoader loader)
=> await loader.LoadAsync(id);
}
// DataLoader — batch N+1 queries
public class ProductByIdDataLoader : BatchDataLoader<int, Product>
{
private readonly IDbContextFactory<ProductDbContext> _factory;
public ProductByIdDataLoader(
IDbContextFactory<ProductDbContext> factory,
IBatchScheduler scheduler)
: base(scheduler)
{
_factory = factory;
}
protected override async Task<IReadOnlyDictionary<int, Product>>
LoadBatchAsync(IReadOnlyList<int> keys, CancellationToken ct)
{
await using var ctx = await _factory.CreateDbContextAsync(ct);
return await ctx.Products
.Where(p => keys.Contains(p.Id))
.ToDictionaryAsync(p => p.Id, ct);
}
}
5.2. Inventory Subgraph — extend Product
// InventorySubgraph/Types/ProductExtension.cs
[ExtendServiceType] // Extend type tu subgraph khac
public class ProductExtension
{
[Key("id")]
public int Id { get; set; }
public async Task<int> GetStock(
[Parent] Product product,
InventoryService service)
=> await service.GetStockAsync(product.Id);
public async Task<string> GetWarehouse(
[Parent] Product product,
InventoryService service)
=> await service.GetWarehouseAsync(product.Id);
[Requires("weight")]
public decimal GetShippingCost(
[Parent] Product product)
=> product.Weight.HasValue
? product.Weight.Value * 2.5m + 5.0m
: 10.0m;
}
5.3. Gateway voi HotChocolate Fusion
// Gateway/Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddGraphQLServer()
.AddApolloFederationGateway()
.AddServiceEndpoint(
"products", new Uri("http://product-service/graphql"))
.AddServiceEndpoint(
"inventory", new Uri("http://inventory-service/graphql"))
.AddServiceEndpoint(
"reviews", new Uri("http://review-service/graphql"))
.AddServiceEndpoint(
"sellers", new Uri("http://seller-service/graphql"));
var app = builder.Build();
app.MapGraphQL();
app.Run();
DataLoader — giai phap cho N+1 trong Federation
Khi Router fan-out query den nhieu subgraph, moi subgraph co the nhan hang tram entity resolution request cung luc (vi du: resolve stock cho 50 products trong 1 list query). Neu khong dung DataLoader, moi entity = 1 database query → N+1 problem.
HotChocolate co built-in DataLoader tu dong batch cac request trong cung 1 execution cycle. 50 entity resolutions → 1 SQL query voi WHERE id IN (...). Day la khac biet lon giua PoC va production-grade Federation.
6. Hieu nang — Federation co cham hon REST?
Mot lo ngai pho bien: "them 1 lop Router co lam cham khong?" Cau tra loi ngan: latency tang nhe o Router, nhung tong thoi gian end-to-end thuong giam nho parallel fan-out va loai bo waterfall tu frontend.
| Kich ban | REST (5 API calls) | GraphQL Federation | Chenh lech |
|---|---|---|---|
| Product detail page | ~450ms (5 sequential calls) | ~180ms (1 query, parallel fan-out) | -60% |
| Product list (20 items) | ~200ms (1 call, over-fetching) | ~220ms (1 query, exact fields) | +10% (nhung it data transfer) |
| Mobile — mang yeu | ~2.5s (5 round-trips x latency cao) | ~800ms (1 round-trip) | -68% |
| Router overhead | N/A | ~3-8ms (query planning) | Khong dang ke |
graph LR
subgraph "REST Pattern"
R1["Call 1: 90ms"] --> R2["Call 2: 85ms"]
R2 --> R3["Call 3: 95ms"]
R3 --> R4["Call 4: 80ms"]
R4 --> R5["Call 5: 100ms"]
end
subgraph "Federation Pattern"
Q["Query: 5ms planning"]
Q --> F1["Fan-out 1: 90ms"]
Q --> F2["Fan-out 2: 85ms"]
Q --> F3["Fan-out 3: 95ms"]
Q --> F4["Fan-out 4: 80ms"]
F1 --> M["Merge: 2ms"]
F2 --> M
F3 --> M
F4 --> M
M --> F5["Fan-out 5: 100ms"]
F5 --> M2["Final: 2ms"]
end
style Q fill:#e94560,stroke:#fff,color:#fff
style M fill:#4CAF50,stroke:#fff,color:#fff
style M2 fill:#4CAF50,stroke:#fff,color:#fff
Hinh 4: REST sequential (450ms) vs Federation parallel fan-out (~200ms)
Benchmark thuc te tu The Guild (2026)
The Guild (tac gia GraphQL Hive) cong bo benchmark so sanh cac Federation router:
Apollo Router (Rust): ~8,500 req/s, p99 latency ~12ms
Cosmo Router (Go): ~7,200 req/s, p99 latency ~15ms
Hive Gateway (TypeScript): ~3,800 req/s, p99 latency ~28ms
Cac con so nay cho thay Router overhead khong phai bottleneck — subgraph response time va database queries moi la yeu to quyet dinh.
7. Caching — Thach thuc lon nhat cua GraphQL
Diem yeu noi tieng cua GraphQL: CDN caching kho. REST dung GET + Cache-Control header rat de cache. GraphQL dung POST, moi query la body khac nhau.
| Chien luoc | Mo ta | Hieu qua |
|---|---|---|
| Persisted Queries | Hash query thanh ID (vi du abc123), gui GET /graphql?id=abc123. CDN cache duoc nhu REST. | Cao — phu hop production |
| Response Caching | Cache toan bo response tai Router theo query hash + variables. | Trung binh — cache hit rate thap voi queries da dang |
| Entity Caching | Cache tung entity (vi du Product:123) tai Router. Khi query khac cung can Product:123, dung cache thay vi goi subgraph. | Cao — Apollo Router ho tro voi Redis |
| Subgraph-level Caching | Moi subgraph tu cache tai data layer (Redis, in-memory). | Cao — kiem soat toan dien, khong phu thuoc Router |
// Apollo Router — cau hinh Entity Caching voi Redis
// router.yaml
supergraph:
introspection: false
preview_entity_cache:
enabled: true
redis:
urls: ["redis://cache-redis:6379"]
ttl: 300s
subgraph:
products:
enabled: true
ttl: 600s
inventory:
enabled: true
ttl: 30s # stock thay doi thuong xuyen
reviews:
enabled: true
ttl: 300s
8. Apollo Connectors — Tich hop REST vao Federation
Mot tinh nang dot pha cua Apollo Router v2: Connectors cho phep bien REST API thanh subgraph ma khong can viet code. Chi can khai bao trong schema:
# Bien REST endpoint thanh GraphQL subgraph
# Khong can server rieng — Router tu goi REST API
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.11", import: ["@key"])
@link(url: "https://specs.apollo.dev/connect/v0.4", import: ["@source", "@connect"])
@source(name: "legacy", http: { baseURL: "https://api.legacy.com/v1" })
type Product @key(fields: "id") {
id: ID!
name: String!
legacyData: LegacyProductData
@connect(
source: "legacy"
http: { GET: "/products/{$this.id}" }
selection: """
sku
category: categoryName
tags: labels
"""
)
}
type LegacyProductData {
sku: String
category: String
tags: [String]
}
Connectors cho phep migrate dan dan
Thay vi rewrite toan bo REST API sang GraphQL (rui ro cao), ban co the:
1. Giu nguyen REST services hien tai.
2. Dung Connectors de expose chung qua Federation.
3. Frontend chuyen sang GraphQL query.
4. Dan dan thay the REST services bang native GraphQL subgraphs.
Day la chien luoc "strangler fig pattern" ap dung cho API migration.
9. Best Practices cho Production
9.1. Schema Design
# DO: Dinh nghia entity ro rang voi @key
type Product @key(fields: "id") {
id: ID!
name: String!
}
# DO: Dung @shareable cho computed fields
type Product @key(fields: "id") {
id: ID!
displayPrice: String @shareable # nhieu subgraph co the resolve
}
# DON'T: Khong tao "God subgraph" chua qua nhieu type
# Moi subgraph nen focus vao 1 bounded context (DDD)
9.2. Observability
# Apollo Router — OpenTelemetry config
# router.yaml
telemetry:
instrumentation:
spans:
router:
attributes:
graphql.document: true
subgraph:
attributes:
subgraph.name: true
subgraph.graphql.document: true
exporters:
tracing:
otlp:
enabled: true
endpoint: http://otel-collector:4317
protocol: grpc
9.3. Error Handling
// HotChocolate — Error filter cho Federation
public class GraphQLErrorFilter : IErrorFilter
{
public IError OnError(IError error)
{
return error.WithCode(error.Exception switch
{
NotFoundException => "NOT_FOUND",
UnauthorizedException => "UNAUTHORIZED",
ValidationException => "VALIDATION_ERROR",
_ => "INTERNAL_ERROR"
})
.RemoveExtension("stackTrace"); // khong leak stack trace
}
}
10. Khi nao nen dung Federation?
| Kich ban | Nen dung | Khong nen dung |
|---|---|---|
| So luong services | 5+ microservices, nhieu team | Monolith hoac 2-3 services |
| Frontend | Nhieu platform (web, mobile, partner API) | 1 frontend duy nhat, it trang phuc tap |
| Data composition | 1 trang can data tu 3+ services | Moi trang chi can data tu 1 service |
| Team structure | Moi team so huu service rieng, can tu chu | 1 team nho quan ly tat ca |
| Caching | Data thay doi thuong xuyen, personalized | Hau het la static content, can CDN cache manh |
| Performance | Can giam round-trips, loai bo over-fetching | API don gian, latency da thap |
Federation khong phai silver bullet
1. Complexity tang: Them Router, schema composition, entity resolution — doi hoi team hieu sau ve GraphQL, khong chi biet REST.
2. Debugging kho hon: Khi query cham, can trace qua Router → subgraph → database. Khong co observability tot se rat kho debug.
3. Schema governance: Voi 10+ subgraphs tu nhieu team, can quy trinh review schema changes (schema registry, CI checks) de tranh breaking changes.
4. Learning curve: Developer can hoc GraphQL spec, Federation directives, DataLoader pattern, va caching strategies — khoi luong kien thuc khong nho.
5. Khong thay the API Gateway hoan toan: Van can API Gateway cho authentication, rate limiting, IP filtering. Router chi xu ly GraphQL routing.
11. So sanh tong quan cac approach
| Approach | Data Composition | Team Autonomy | Caching | Complexity |
|---|---|---|---|---|
| REST + API Gateway | Frontend tu ghep | Cao | CDN de dang | Thap |
| REST + BFF | BFF layer ghep | Trung binh (BFF la bottleneck) | CDN + BFF cache | Trung binh |
| GraphQL (single server) | Server ghep | Thap (1 team so huu schema) | Kho | Trung binh |
| GraphQL Federation | Router tu dong ghep | Cao (moi team 1 subgraph) | Kho (can persisted queries) | Cao |
| gRPC + BFF | BFF layer ghep | Cao | Khong co CDN | Cao |
12. Ket luan
GraphQL Federation giai quyet bai toan ma REST API Gateway khong lam duoc: tu dong compose du lieu tu nhieu microservices thanh 1 response duy nhat, trong khi van giu cho moi team quyen tu chu voi subgraph rieng. Voi Apollo Router (Rust, 10x nhanh hon gateway cu), HotChocolate Fusion (tich hop sau voi .NET 10), va cac tinh nang nhu Connectors (tich hop REST khong can code), Federation da san sang cho production tai quy mo lon.
Tuy nhien, Federation khong phai cho moi du an. Neu he thong cua ban chi co 2-3 services va 1 frontend — REST + BFF don gian hon nhieu. Federation phat huy gia tri thuc su khi ban co 5+ services, nhieu team, va frontend can data tu nhieu nguon. Hay bat dau voi 1-2 subgraph, validate gia tri, roi mo rong dan — dung co migrate toan bo he thong cung luc.
Nguon tham khao
- Introduction to Apollo Federation — Apollo GraphQL Docs
- What's New in GraphOS Router v2.x — Apollo GraphQL Docs
- GraphQL Federation Gateways Performance Benchmark — The Guild
- Hot Chocolate vs graphql-dotnet in .NET 2026 — Coding Droplets
- GraphQL vs REST: 18 Claims Fact-Checked — WunderGraph
- GraphQL vs REST 2026: Performance Tested — Tech Insider
- GraphQL vs REST vs gRPC: 2026 Decision Framework — Fordel Studios
- Federating .NET GraphQL APIs with Fusion — Medium
.NET 11 Preview 3: Union Type, Runtime Async và loạt cải tiến đáng chú ý
Distributed Locking — Giải quyết Race Condition trong hệ thống phân tán với Redis và .NET 10
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.