GraphQL Federation #

Saat tim kecil mengelola satu GraphQL schema, semuanya terasa mudah — satu repo, satu deployment, satu tim yang bertanggung jawab. Tapi saat organisasi bertumbuh menjadi puluhan tim dengan domain yang berbeda-beda, schema monolith itu menjadi bottleneck: setiap perubahan membutuhkan koordinasi, deployment satu tim memblokir tim lain, dan schema raksasa semakin susah dipahami. GraphQL Federation adalah jawaban untuk masalah skala ini — cara untuk memecah schema besar menjadi subgraph-subgraph yang dimiliki tim berbeda, tapi tetap menyajikan satu endpoint GraphQL yang terpadu ke client. Artikel ini membahas bagaimana Federation bekerja, directive-directive kuncinya, query planning, schema governance, dan kapan kompleksitas Federation memang layak untuk ditanggung.

Masalah yang Dipecahkan Federation #

GraphQL monolith bekerja dengan baik sampai pada titik tertentu. Setelah titik itu, masalahnya muncul satu per satu:

GraphQL Monolith di organisasi besar — masalah yang muncul:

1. Bottleneck deployment
   Tim User, Tim Order, Tim Payment semuanya harus merge ke satu repo
   → Satu PR yang rusak memblokir semua tim lain dari deploy
   → Release cycle melambat seiring bertambahnya tim

2. Koordinasi schema yang mahal
   Perubahan type User oleh Tim User harus dikomunikasikan ke semua tim
   yang menggunakan atau meng-extend type User
   → Meeting koordinasi berulang
   → Schema changes menjadi pekerjaan besar

3. Satu codebase yang tak terkendali
   Resolver untuk User, Order, Payment, Inventory, Notification semua dalam satu file
   → Tidak ada batas yang jelas antara domain
   → Bug di satu domain bisa impact seluruh schema

4. Tidak ada ownership yang jelas
   "Siapa yang bertanggung jawab untuk type ini?"
   "Tim mana yang harus di-ping saat ada issue di resolver ini?"

GraphQL Federation memecahkan ini dengan memungkinkan setiap tim memiliki subgraph mereka sendiri — GraphQL service yang independent, deployed secara terpisah, tapi berkontribusi pada satu supergraph yang terpadu.


Arsitektur Federation: Supergraph, Subgraph, dan Router #

flowchart TD
    Client["Client\n(Web / Mobile)"]
    Router["Apollo Router / Gateway\n(Supergraph)\nQuery Planning & Orchestration"]

    subgraph UserSG["User Subgraph\n(Tim User)"]
        US["User Service\ntype User @key(fields: 'id')\nquery { user(id: ID!): User }"]
    end

    subgraph OrderSG["Order Subgraph\n(Tim Order)"]
        OS["Order Service\ntype Order @key(fields: 'id')\ntype User @key(fields: 'id') @extends"]
    end

    subgraph ProductSG["Product Subgraph\n(Tim Product)"]
        PS["Product Service\ntype Product @key(fields: 'id')\ntype Order @key(fields: 'id') @extends"]
    end

    subgraph ReviewSG["Review Subgraph\n(Tim Review)"]
        RS["Review Service\ntype Review\ntype Product @key(fields: 'id') @extends"]
    end

    Client -->|"POST /graphql"| Router
    Router -->|"Subquery"| UserSG
    Router -->|"Subquery"| OrderSG
    Router -->|"Subquery"| ProductSG
    Router -->|"Subquery"| ReviewSG

    style Router fill:#E67E22,color:#fff,stroke:#D35400
    style Client fill:#2C3E50,color:#fff

Subgraph adalah GraphQL service biasa yang ditambahkan directive Federation. Setiap subgraph memiliki schema sendiri, deployment sendiri, dan tim yang bertanggung jawab. Mereka tidak tahu satu sama lain secara langsung — komunikasi terjadi melalui Router.

Router (Apollo Router atau Apollo Gateway) adalah komponen yang menerima query dari client, memahami seluruh supergraph schema, membuat query plan, dan mendistribusikan subquery ke subgraph yang relevan. Client hanya berbicara dengan Router — mereka tidak tahu berapa banyak subgraph yang ada di belakang.

Supergraph adalah schema gabungan yang dihasilkan dari komposisi semua subgraph. Router membutuhkan supergraph schema ini untuk bisa membuat query plan.


Federation Directive — Kosakata Kunci #

Directive adalah cara subgraph mengkomunikasikan informasi ke Router tentang bagaimana type dan field mereka berhubungan dengan subgraph lain.

@key — Mendefinisikan Entity #

@key adalah directive paling fundamental. Ia menandai sebuah type sebagai entity — type yang bisa direferensikan oleh subgraph lain menggunakan identifier tertentu.

# Di User Subgraph
type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
  createdAt: String!
}

type Query {
  user(id: ID!): User
  users: [User!]!
}

Dengan @key(fields: "id"), subgraph lain bisa meng-extend type User menggunakan field id sebagai kunci untuk menemukan dan menggabungkan data.

@extends, @external, dan @requires #

Tiga directive ini bekerja bersama saat satu subgraph ingin meng-extend entity yang di-define di subgraph lain.

# Di Order Subgraph
# User didefinisikan di User Subgraph, tapi Order Subgraph ingin menambahkan field

type User @key(fields: "id") @extends {
  id: ID! @external       # field ini berasal dari User Subgraph
  orders: [Order!]!       # field baru yang di-provide Order Subgraph
}

type Order @key(fields: "id") {
  id: ID!
  total: Float!
  status: OrderStatus!
  user: User!
}

type Query {
  order(id: ID!): Order
}
# Di Review Subgraph dengan @requires
# Review butuh informasi dari subgraph lain untuk resolver-nya

type Product @key(fields: "id") @extends {
  id: ID! @external
  name: String! @external    # butuh nama product dari Product Subgraph
  averageRating: Float!      # dihitung dari reviews
    @requires(fields: "name")  # requires 'name' dari Product Subgraph
  reviews: [Review!]!
}

@requires memberitahu Router: “Untuk resolve field ini, saya butuh field name dari subgraph yang meng-own Product terlebih dahulu.” Router akan memastikan data tersebut tersedia sebelum memanggil Review Subgraph.

@provides — Optimasi Query Planning #

@provides adalah hint ke Router bahwa subgraph ini bisa menyediakan field tertentu dari entity lain tanpa harus memanggil subgraph yang meng-own entity tersebut.

# Di Order Subgraph
type Order @key(fields: "id") {
  id: ID!
  user: User! @provides(fields: "name email")
  # Order Subgraph menyimpan denormalized copy dari user.name dan user.email
  # Router bisa skip panggil ke User Subgraph jika hanya butuh name dan email
}

type User @key(fields: "id") @extends {
  id: ID! @external
  name: String! @external
  email: String! @external
}

Query Planning — Bagaimana Router Mengeksekusi Query #

Ini adalah mekanisme paling menarik dari Federation — Router tidak hanya mem-proxy request, ia secara aktif membuat query plan yang optimal.

sequenceDiagram
    participant C as Client
    participant R as Router
    participant US as User Subgraph
    participant OS as Order Subgraph
    participant PS as Product Subgraph

    C->>R: query { user(id: "1") { name orders { total product { name } } } }

    Note over R: Router membuat query plan:
    Note over R: Step 1: Fetch user dari User Subgraph
    Note over R: Step 2: Fetch orders dari Order Subgraph (pakai user.id)
    Note over R: Step 3: Fetch products dari Product Subgraph (pakai product.id dari orders)

    R->>US: query { user(id: "1") { name id } }
    US-->>R: { user: { id: "1", name: "Budi" } }

    R->>OS: query { _entities(representations: [{__typename: "User", id: "1"}]) { ... on User { orders { total product { id } } } } }
    OS-->>R: { orders: [{ total: 150000, product: { id: "p1" } }] }

    R->>PS: query { _entities(representations: [{__typename: "Product", id: "p1"}]) { ... on Product { name } } }
    PS-->>R: { name: "Laptop" }

    Note over R: Merge semua hasil
    R-->>C: { user: { name: "Budi", orders: [{ total: 150000, product: { name: "Laptop" } }] } }

Kunci dari query planning adalah _entities query yang secara otomatis di-generate oleh setiap subgraph yang memiliki entity. Router menggunakan ini untuk “melanjutkan” resolusi entity di subgraph yang tepat.


Entity Resolution — Cara Subgraph Menghandle _entities #

Setiap subgraph yang mendefinisikan entity harus mengimplementasikan reference resolver — fungsi yang menerima representasi entity (minimal field @key-nya) dan mengembalikan data lengkap.

// User Subgraph — reference resolver
const resolvers = {
  User: {
    // Router akan memanggil ini saat butuh resolve entity User
    __resolveReference(reference) {
      // reference = { __typename: "User", id: "123" }
      // Fetch data berdasarkan id yang diberikan Router
      return users.findById(reference.id)
    }
  },
  Query: {
    user: (_, { id }) => users.findById(id)
  }
}

// Order Subgraph — extend User dan tambahkan field orders
const resolvers = {
  User: {
    __resolveReference(reference) {
      return { id: reference.id }  // cukup kembalikan representasi
    },
    orders(user) {
      // Fetch semua orders milik user ini
      return orders.findByUserId(user.id)
    }
  }
}

Schema Composition dan Governance #

Salah satu risiko terbesar Federation adalah schema conflict — dua subgraph mendefinisikan type yang sama tapi dengan cara yang tidak kompatibel. Schema governance meminimalkan risiko ini.

flowchart LR
    subgraph Dev["Development Flow"]
        SGCode["Subgraph Code\n+ Schema Changes"]
        CI["CI Pipeline"]
        SR["Schema Registry\n(Apollo Studio)"]
        Check{"Schema\nCompatibility\nCheck"}
        Deploy["Deploy Subgraph"]
    end

    SGCode --> CI
    CI --> SR
    SR --> Check
    Check -->|"Compatible\nNo breaking changes"| Deploy
    Check -->|"Conflict detected\nor breaking change"| Fail["Build Gagal\nDeveloper Dinotifikasi"]

    style Check fill:#E67E22,color:#fff
    style Deploy fill:#27AE60,color:#fff
    style Fail fill:#E74C3C,color:#fff

Schema Registry #

Schema registry adalah komponen yang menyimpan semua subgraph schema dan melakukan schema composition — proses menggabungkan semua subgraph menjadi supergraph dan memvalidasi bahwa tidak ada konflik.

Yang divalidasi saat komposisi:
  ✓ Tidak ada type yang didefinisikan ulang dengan cara yang konflik
  ✓ Semua @key field ada di subgraph yang meng-own type tersebut
  ✓ Semua @external field benar-benar ada di subgraph yang meng-ownnya
  ✓ Semua @requires field tersedia dari subgraph yang tepat

Contoh konflik yang dicegah:
  User Subgraph: type User { id: ID!, name: String! }
  Order Subgraph: type User { id: ID!, name: Int! }  ← konflik: name tipe berbeda
  → Schema composition gagal → subgraph tidak bisa di-deploy ke production

Perbandingan Pendekatan #

GraphQL Federation vs GraphQL Monolith #

GraphQL Monolith — cocok untuk:
  ✓ Tim kecil (1–3 tim yang bisa koordinasi dengan mudah)
  ✓ Domain yang belum stabil (schema masih sering berubah besar)
  ✓ Produk early-stage yang belum perlu microservice
  ✓ Tim yang belum familiar dengan Federation complexity

GraphQL Federation — cocok untuk:
  ✓ Organisasi dengan 4+ tim yang punya domain berbeda
  ✓ Microservice architecture yang sudah ada
  ✓ Tim yang butuh deploy secara independent tanpa koordinasi
  ✓ Schema yang sudah cukup stabil per domain

GraphQL Federation vs REST API Gateway #

REST API Gateway:
  ✓ Lebih sederhana secara konseptual
  ✓ Tidak butuh tim yang paham GraphQL
  ✓ Caching HTTP lebih mudah
  ✗ Client perlu multiple request untuk data dari berbagai service
  ✗ Tidak ada type system yang kuat
  ✗ Over-fetching dan under-fetching tetap ada

GraphQL Federation:
  ✓ Satu query untuk data dari banyak service
  ✓ Type system yang kuat mencegah breaking changes tidak terdeteksi
  ✓ Client-driven data fetching
  ✗ Learning curve lebih tinggi
  ✗ Kompleksitas infrastructure lebih tinggi
  ✗ Debugging lebih sulit

Anti-Pattern Federation yang Harus Dihindari #

Subgraph yang Terlalu Granular #

//  Anti-pattern: terlalu banyak subgraph kecil
UserProfileSubgraph      hanya type UserProfile
UserPreferencesSubgraph  hanya type UserPreferences
UserAvatarSubgraph       hanya type UserAvatar
 Setiap query tentang user butuh resolve ke 3+ subgraph
 Overhead jaringan tinggi
 Tidak ada organizational benefit karena tim yang sama mengelola ketiganya

//  Solusi: Subgraph sesuai dengan bounded context / domain
UserSubgraph  UserProfile, UserPreferences, UserAvatar, UserAddress
 Satu subgraph, satu tim, satu domain yang kohesif

Circular Dependency Antar Subgraph #

//  Anti-pattern: circular dependency
// User Subgraph @requires field dari Order Subgraph
// Order Subgraph @requires field dari User Subgraph
//  Query plan tidak bisa dibuat, circular dependency

type User @key(fields: "id") @extends {
  // ... di Order Subgraph
  totalSpent: Float! @requires(fields: "recentOrderCount")  // dari User Subgraph?
}

//  Solusi: Denormalize data atau gunakan @provides
// Jika Order Subgraph butuh data User, ambil dari User Subgraph (bukan sebaliknya)
// Jika perlu aggregate, lakukan di sisi yang "membutuhkan", bukan yang "dimiliki"

Business Logic di Router/Gateway #

//  Anti-pattern: Router sebagai orchestration layer dengan business logic
// Router bukan tempat untuk:
//   - Transformasi data kompleks
//   - Business rules
//   - Data aggregation yang custom
//   - Rate limiting per-feature

//  Router hanya untuk:
//   - Query planning dan routing
//   - Basic authentication check (validasi token)
//   - Rate limiting di level global
//   - Error normalization
//   - Logging dan tracing

Tidak Ada Schema Governance #

// ✗ Anti-pattern: deploy subgraph tanpa schema check
Tim Order langsung deploy perubahan yang menghapus field `Order.user`
→ Client yang menggunakan field itu langsung error
→ Tim User tidak tahu bahwa field yang mereka depend sudah hilang

// ✓ Solusi: Schema check di CI pipeline
Setiap PR yang mengubah subgraph schema wajib melalui:
  1. apollo schema check (cek kompatibilitas dengan schema registry)
  2. Cek breaking changes terhadap semua subgraph lain
  3. Cek apakah ada client yang menggunakan field yang dihapus
  → PR hanya bisa merge jika semua check hijau
Breaking change di Federation lebih berbahaya dari breaking change di GraphQL biasa karena dampaknya bisa lintas subgraph. Menghapus field @key di satu subgraph bisa membuat subgraph lain yang meng-extend entity tersebut tidak bisa meresolvei entity sama sekali. Schema registry dan automated schema check di CI adalah keharusan, bukan opsional.

Observability di Federation #

Karena satu query client bisa mengakibatkan beberapa subquery ke beberapa subgraph, observability di Federation membutuhkan pendekatan yang berbeda dari GraphQL biasa.

Yang perlu dipantau di Federation:

1. Per query (di Router level):
   → Total latency dari perspektif client
   → Query plan yang digunakan (berapa subgraph yang di-call)
   → Error rate per operasi

2. Per subgraph (di setiap subgraph):
   → Resolver latency per field
   → Entity resolution latency (untuk @key queries)
   → Error rate

3. Cross-subgraph:
   → Berapa kali subgraph A memanggil subgraph B
   → Distributed trace dari query client sampai ke semua subgraph

4. Schema usage:
   → Field mana yang paling sering digunakan (untuk schema governance)
   → Field mana yang tidak pernah digunakan (kandidat untuk deprecated)
Apollo Studio menyediakan distributed tracing dan field usage analytics built-in untuk Federation. Jika kamu tidak menggunakan Apollo Studio, kamu perlu setup OpenTelemetry sendiri di Router dan setiap subgraph untuk mendapatkan visibility yang sama. Tanpa distributed tracing, debugging performance issue di Federation sangat sulit.

Kapan Menggunakan Federation vs Tidak #

Gunakan GraphQL Federation jika:
  ✓ Ada 4+ tim dengan domain yang berbeda dan butuh deploy secara independent
  ✓ Sudah ada microservice architecture yang mature
  ✓ Schema monolith sudah menjadi bottleneck nyata (bukan asumsi)
  ✓ Tim engineering sudah familiar dengan GraphQL dan siap belajar Federation
  ✓ Ada kebutuhan untuk governance schema lintas tim
  ✓ Organisasi cukup besar untuk justifikasi infrastructure overhead

Tetap dengan GraphQL Monolith jika:
  ✓ Tim kecil (< 4 tim yang perlu kontribusi ke schema)
  ✓ Domain belum stabil — masih banyak perubahan schema besar
  ✓ Produk masih early-stage, belum ada kebutuhan microservice
  ✓ Tim belum familiar dengan GraphQL sama sekali

Gunakan REST + API Gateway (bukan Federation) jika:
  ✓ Tim tidak familiar dengan GraphQL dan tidak ada bandwidth untuk belajar
  ✓ Data fetching pola yang sederhana, tidak ada kebutuhan join lintas domain
  ✓ Caching HTTP adalah requirement utama
  ✓ Public API yang dikonsumsi banyak pihak eksternal dengan tools beragam

Checklist GraphQL Federation #

ARSITEKTUR DAN DESAIN:
  □ Subgraph dibagi berdasarkan bounded context / domain, bukan berdasarkan tabel DB
  □ Setiap subgraph dimiliki oleh satu tim yang jelas
  □ Tidak ada circular dependency antar subgraph
  □ @key fields menggunakan identifier yang stabil (bukan yang bisa berubah)

FEDERATION DIRECTIVE:
  □ @key digunakan untuk semua entity yang bisa direferensikan subgraph lain
  □ @external hanya digunakan untuk field yang benar-benar berasal dari subgraph lain
  □ @requires digunakan dengan hati-hati — setiap @requires menambah subgraph call
  □ @provides digunakan untuk optimasi query plan jika data tersedia secara denormalized

SCHEMA GOVERNANCE:
  □ Schema registry digunakan (Apollo Studio atau alternatif)
  □ Schema check wajib di CI pipeline sebelum merge
  □ Breaking changes wajib melalui deprecation period
  □ Field usage metrics tersedia untuk membuat keputusan schema

ROUTER / GATEWAY:
  □ Router tidak mengandung business logic
  □ Auth header dipropagasi ke semua subgraph
  □ Query complexity limiting diimplementasikan di level Router
  □ Rate limiting di level global ada

OBSERVABILITY:
  □ Distributed tracing dari Router ke semua subgraph
  □ Latency per subgraph dipantau
  □ Error rate per operasi dipantau
  □ Query plan logging tersedia untuk debugging

DEPLOYMENT:
  □ Subgraph bisa di-deploy secara independent
  □ Schema composition dijalankan di CI sebelum deploy
  □ Rollback subgraph individual bisa dilakukan tanpa mempengaruhi subgraph lain

Ringkasan #

  • Federation memecah GraphQL monolith menjadi subgraph yang dimiliki tim berbeda — setiap subgraph independent secara deployment tapi berkontribusi pada satu supergraph yang terpadu bagi client.
  • Router membuat query plan secara otomatis — client tidak perlu tahu berapa banyak subgraph yang ada. Router yang memutuskan subgraph mana yang perlu dipanggil dan dalam urutan apa.
  • @key adalah directive terpenting — ia menandai entity yang bisa direferensikan lintas subgraph. Setiap subgraph yang meng-extend entity lain butuh @key dari subgraph yang meng-own entity tersebut.
  • @requires menambah latency — setiap @requires memaksa Router memanggil subgraph dalam urutan tertentu (sequential, bukan parallel). Gunakan dengan hemat.
  • Subgraph seharusnya sesuai dengan bounded context domain — bukan berdasarkan tabel database atau endpoint REST yang ada. Terlalu granular menciptakan overhead; terlalu besar menghilangkan manfaat Federation.
  • Schema governance bukan opsional — schema registry dan automated schema check di CI adalah kebutuhan minimum. Breaking change di Federation bisa merusak lintas subgraph secara tidak terduga.
  • Router adalah thin layer — business logic, transformasi data, dan orchestration kompleks tidak boleh ada di Router. Router hanya untuk query planning, basic auth check, dan observability.
  • Distributed tracing wajib — tanpa visibility lintas subgraph, debugging performance issue di Federation sangat sulit. Setiap subquery harus bisa dilacak sebagai bagian dari query client yang lebih besar.
  • Federation bukan untuk semua organisasi — ia optimal untuk 4+ tim dengan domain mature dan microservice architecture yang sudah ada. Untuk tim kecil atau produk early-stage, GraphQL monolith lebih sederhana dan lebih tepat.
  • Federation adalah keputusan arsitektur sekaligus organisasi — ia mengubah cara tim berkolaborasi, cara schema di-govern, dan cara infrastructure di-manage. Adopt dengan kesadaran penuh tentang tradeoff yang datang bersamanya.

← Sebelumnya: OAuth   Berikutnya: API Security →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact