GraphQL Federation — GraphQL

GraphQL Federation Di microservices, setiap service punya schema sendiri. Federation menggabungkan multiple schema menjadi satu unified graph yang client…

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 } }. title milik subgraph inventory, averageRating milik 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
}

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
            }
          }
        }
      },
    },
  },
}

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

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

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.

Yang akan kamu pelajari