GraphQL Federation — Xây dựng API Gateway thống nhất cho Microservices

Posted on: 4/25/2026 6:12:31 AM

GraphQL Federation Microservices API Gateway System Design

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

1Request tu frontend
340%Tang truong adoption tai Fortune 500
10xApollo Router nhanh hon Gateway cu
60%GitHub giam response time voi GraphQL

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 niemMo taVi du
SubgraphGraphQL 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 }
SupergraphSchema 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 }
RouterRuntime 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)
@keyDirective danh dau entity — cho phep cac subgraph khac reference va extend type nay.type Product @key(fields: "id")
@externalField duoc dinh nghia o subgraph khac, chi reference tai day.extend type Product { id: ID! @external }
@requiresField 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 nangFederation v1Federation v2
Shared typesMoi type chi duoc 1 subgraph dinh nghia@shareable — nhieu subgraph co the dinh nghia cung 1 field
Schema evolutionBreaking changes kho kiem soat@override — migrate field giua cac subgraph an toan
Input typesKhong chia se duoc@inaccessible — an field khoi supergraph ma khong xoa
CompositionRuntime composition tai gatewayBuild-time composition — loi duoc bat truoc khi deploy
Error messagesMo ho, kho debugComposition hints chi tiet, chi ro vi tri loi
Progressive migrationKhong 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 chiApollo RouterCosmo RouterHotChocolate Fusion
Ngon nguRustGoC# (.NET)
LicenseElastic v2 (source-available)Apache 2.0 (open-source)MIT (open-source)
Hieu nangRat cao (Rust runtime)Cao (Go runtime)Cao (Kestrel + .NET 10)
Federation specv2.11 (tac gia spec)v2.x compatiblev2.x + Fusion rieng
ConnectorsREST Connectors (GraphOS)KhongSchema Stitching legacy
Managed platformApollo GraphOS (tra phi)Cosmo Cloud (co free tier)Tu host
Phu hopTeam da ngon ngu, enterpriseTeam can open-source thuc suTeam .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 banREST (5 API calls)GraphQL FederationChenh 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 overheadN/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 luocMo taHieu qua
Persisted QueriesHash query thanh ID (vi du abc123), gui GET /graphql?id=abc123. CDN cache duoc nhu REST.Cao — phu hop production
Response CachingCache toan bo response tai Router theo query hash + variables.Trung binh — cache hit rate thap voi queries da dang
Entity CachingCache 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 CachingMoi 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 banNen dungKhong nen dung
So luong services5+ microservices, nhieu teamMonolith hoac 2-3 services
FrontendNhieu platform (web, mobile, partner API)1 frontend duy nhat, it trang phuc tap
Data composition1 trang can data tu 3+ servicesMoi trang chi can data tu 1 service
Team structureMoi team so huu service rieng, can tu chu1 team nho quan ly tat ca
CachingData thay doi thuong xuyen, personalizedHau het la static content, can CDN cache manh
PerformanceCan giam round-trips, loai bo over-fetchingAPI 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

ApproachData CompositionTeam AutonomyCachingComplexity
REST + API GatewayFrontend tu ghepCaoCDN de dangThap
REST + BFFBFF layer ghepTrung binh (BFF la bottleneck)CDN + BFF cacheTrung binh
GraphQL (single server)Server ghepThap (1 team so huu schema)KhoTrung binh
GraphQL FederationRouter tu dong ghepCao (moi team 1 subgraph)Kho (can persisted queries)Cao
gRPC + BFFBFF layer ghepCaoKhong co CDNCao

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