Federation menggabungkan beberapa server GraphQL (subgraph), masing-masing milik satu tim, menjadi satu graph (supergraph) di balik satu endpoint. Sebuah router menerima query client, memecahnya, menanyakan tiap bagian ke subgraph yang tepat, lalu menyatukan jawabannya. Bayangin Toko Buku sudah besar: tim gudang mengurus judul, harga, dan stok; tim komunitas mengurus ulasan pembaca. Pelanggan cukup bertanya ke satu meja informasi, dan petugasnya tahu bagian mana yang harus ditanya, dalam urutan apa, lalu menyerahkan satu jawaban utuh.
🧪 Tebakan cepat: Client mengirim
{ books { title averageRating } }.titlemilik subgraph inventory,averageRatingmilik subgraph reviews. Berapa request yang dikirim router ke subgraph, dan dalam urutan apa? Jawabannya ada di bagian "Query plan".
Subgraph dan entity
Ini dua subgraph Toko Buku dengan Apollo Federation versi 2:
# subgraph inventory (tim gudang)
extend schema @link(url: "https://specs.apollo.dev/federation/v2.12", import: ["@key"])
type Query {
books: [Book!]!
book(id: ID!): Book
}
type Book @key(fields: "id") {
id: ID!
title: String!
price: Int!
stock: Int!
}
# subgraph reviews (tim komunitas)
extend schema @link(url: "https://specs.apollo.dev/federation/v2.12", import: ["@key"])
type Query {
latestReviews: [Review!]!
}
type Review {
id: ID!
rating: Int!
comment: String!
book: Book!
}
type Book @key(fields: "id") {
id: ID!
reviews: [Review!]!
averageRating: Float
}
Bookadalah entity: type yang dimiliki lebih dari satu subgraph dan bisa dirujuk lintas subgraph.@key(fields: "id")bilang bahwaidcukup untuk menemukan sebuah buku di subgraph mana pun.- Reviews gak tahu judul atau harga. Ia cuma menambah
reviewsdanaverageRatingkeBookyang sama. @linkmengimpor directive Federation dari versi spec tertentu. Di Federation 1, type milik subgraph lain ditulisextend type Book @key(...)dan field key-nya ditandai@external; di versi 2 cukup seperti di atas.
Router dan supergraph
Kedua SDL itu di-compose jadi satu supergraph, misalnya dengan rover supergraph compose atau lewat GraphOS. Client cuma melihat satu schema: Book { id title price stock reviews averageRating }. Router-nya (Apollo Router) memegang supergraph itu, menerima query di satu endpoint, dan untuk tiap query membuat query plan: request apa ke subgraph mana, dan dalam urutan apa. Router gak menyimpan data dan gak menjalankan resolver subgraph; ia cuma merencanakan, mengirim, dan menggabungkan.
Query plan
Ini query plan asli untuk { books { title averageRating reviews { rating } } }, dibuat @apollo/query-planner 2.14.4 dari dua subgraph di atas:
QueryPlan {
Sequence {
Fetch(service: "inventory") {
{
books {
__typename
id
title
}
}
},
Flatten(path: "books.@") {
Fetch(service: "reviews") {
{
... on Book {
__typename
id
}
} =>
{
... on Book {
averageRating
reviews {
rating
}
}
}
},
},
},
}
- Fetch 1, inventory: router meminta
booksdengantitle, ditambah__typenamedanidyang gak diminta client. Itu bahan untuk langkah berikutnya. - Fetch 2, reviews:
Flatten(path: "books.@")berarti "untuk tiap buku di daftar itu". Router mengirim ke reviews field khusus_entitiesdengan variablerepresentations:[{ "__typename": "Book", "id": "1" }, { "__typename": "Book", "id": "2" }, { "__typename": "Book", "id": "3" }]. Sequenceartinya fetch kedua menunggu yang pertama, karena butuh id-nya. Jadi untuk tebakan tadi: dua request, inventory dulu, baru reviews. Itu jawaban tebakannya. Kalau client cuma memintatitledanstock, plan-nya satuFetchke inventory saja.
Arahnya bisa juga terbalik. { latestReviews { rating book { title } } } mulai dari reviews, lalu Flatten(path: "[email protected]") ke inventory untuk judulnya.
__resolveReference
Saat _entities datang, subgraph harus mengubah tiap representation jadi objek. Itu tugas resolver __resolveReference. Dengan @apollo/subgraph (versi 2.15.1):
import { buildSubgraphSchema } from "@apollo/subgraph";
const resolvers = {
Query: {
latestReviews: () => reviews.slice().reverse(),
},
Book: {
// { __typename: "Book", id: "1" } -> objek Book milik subgraph ini
__resolveReference: (ref) => ({ id: ref.id }),
reviews: (book) => reviews.filter((r) => String(r.bookId) === book.id),
averageRating: (book) => average(reviews.filter((r) => String(r.bookId) === book.id)),
},
Review: {
// cukup rujukan: router yang mengambil judulnya dari inventory
book: (review) => ({ __typename: "Book", id: String(review.bookId) }),
},
};
const schema = buildSubgraphSchema({ typeDefs, resolvers });
buildSubgraphSchema menambahkan _entities, union _Entity, dan scalar _Any ke schema subgraph. Di inventory, __resolveReference membaca baris buku dari database, jadi untuk 50 representation ia bisa jadi 50 query: pakai DataLoader dari pelajaran "N+1 Problem & DataLoader".
Directive lain yang akan kamu temui: @shareable (field boleh dijawab lebih dari satu subgraph), @external bersama @requires (field butuh field subgraph lain untuk dihitung) atau @provides, dan @override(from: "...") (memindahkan field dari satu subgraph ke subgraph lain).
Alat dan lisensinya
- Apollo Federation 2: library subgraph
@apollo/subgraphberlisensi MIT. Library composition dan query planner (@apollo/composition,@apollo/query-planner,@apollo/gateway) berlisensi Elastic License 2.0. - Apollo Router: router yang ditulis dengan Rust, versi 2.18.0 (September 2026). Kode sumbernya berlisensi Elastic License 2.0, bukan lisensi open source versi OSI. Router bisa jalan dengan file supergraph lokal. Sejumlah fitur "licensed" (subscription, autentikasi JWT, directive authorization, cache Redis, coprocessor, demand control, safelisting) butuh router tersambung ke GraphOS dengan paket yang mencakupnya. Di paket Free GraphOS (diumumkan April 2025), router self-hosted dibatasi 60 request per menit; request selebihnya dijawab HTTP 503.
- Alternatif: schema stitching dengan
@graphql-tools/stitchdari The Guild (gateway yang menggabungkan schema, aturan penggabungannya ditulis di gateway, bukan lewat directive di subgraph); GraphQL Hive dari The Guild (schema registry dan Hive Gateway, MIT); WunderGraph Cosmo (schema registry dan router yang ditulis dengan Go, Apache 2.0). Hive dan Cosmo sama-sama bisa memakai subgraph bergaya Apollo Federation.
Kapan monolith lebih baik
Federation menambah satu hop (router), proses composition, dan pemeriksaan schema di tiap deploy. Itu sepadan kalau beberapa tim harus men-deploy bagian graph-nya sendiri-sendiri. Untuk satu tim, atau aplikasi yang masih kecil, satu schema GraphQL (monolith, boleh dipecah per modul di dalam kodenya) lebih cepat dan lebih sederhana: satu request, tanpa query plan.
⚠️ Jebakan yang sering ketemu
- N+1 di
__resolveReference. Tiap representation membaca database sendiri-sendiri. Kumpulkan dengan DataLoader. - Key yang gak cocok. Kedua subgraph harus memakai
@keydengan field yang sama (id), dan nilainya harus sama persis ("1"bukan1). - Rantai lintas subgraph yang panjang. Tiap lompatan menambah satu
FetchdalamSequence, dan latensinya menumpuk. - Federation untuk tim kecil. Kerumitan router dan composition gak sepadan kalau semua kode di-deploy oleh tim yang sama.
TL;DR: Federation menggabungkan subgraph milik beberapa tim jadi satu supergraph di balik satu router. Entity seperti Book ditandai @key(fields: "id") di tiap subgraph yang memakainya; subgraph lain cuma menambah field-nya sendiri (di Federation 2 tanpa extend type). Router membuat query plan: { books { title averageRating } } jadi Fetch ke inventory (dengan __typename dan id), lalu Flatten(path: "books.@") ke reviews lewat _entities(representations:), berurutan. Subgraph menjawab representation lewat __resolveReference (pakai DataLoader). Apollo Router dan library composition-nya berlisensi Elastic License 2.0, dan fitur "licensed" router butuh GraphOS; alternatifnya schema stitching, GraphQL Hive, dan WunderGraph Cosmo. Untuk satu tim atau aplikasi kecil, satu schema monolith lebih sederhana.