1. Masalah GraphQL Monolith
GraphQL awalnya dirancang dengan satu monolithic schema yang menggabungkan semua types dan resolvers. Dalam arsitektur microservices, ini menjadi masalah besar karena setiap service memiliki domain data sendiri, dan mengelola satu mega-schema yang mencakup semua service menjadi tidak praktis.
Tantangan GraphQL Monolith
| Tantangan | Dampak |
|---|---|
| Schema coupling | Perubahan di satu service bisa merusak schema global |
| Deployment bottleneck | Semua tim harus koordinasi untuk deploy schema |
| Performance | Satu resolver bisa N+1 query ke banyak service |
| Team autonomy | Tim tidak bisa mandiri mengelola schema mereka |
| Code size | Schema dan resolver membengkak seiring pertumbuhan |
GraphQL Federation cocok jika: tim Anda sudah terbagi per domain (team-based microservices), schema lebih dari 500+ types, atau Anda mengalami scaling bottleneck pada single GraphQL server. Untuk proyek kecil-menengah, monolithic GraphQL masih lebih sederhana dan maintainable.
2. Apollo Federation v2
Apollo Federation adalah standar terdepan untuk menggabungkan beberapa GraphQL services (subgraphs) menjadi satu unified schema (supergraph) yang bisa di-query client secara seamless. Apollo Router bertindak sebagai gateway yang meng-orchestrate query ke subgraphs yang tepat.
Architecture Overview
# Apollo Federation Architecture: # ββββββββββββββββββββββββββββββββββββββββββββββββ # β Client (Browser/App) β # β Query: user + orders + reviews β # ββββββββββββββββββββ¬ββββββββββββββββββββββββββββ # β # βΌ # ββββββββββββββββββββββββββββββββββββββββββββββββ # β Apollo Router (Gateway) β # β β # β - Parse query β # β - Query planning β subgraph routing β # β - Parallel execution ke subgraphs β # β - Merge results β # β - Return unified response β # βββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬ββββββββββ # β β β β # βΌ βΌ βΌ βΌ # ββββββββββ ββββββββββ ββββββββββ ββββββββββ # β Users β β Orders β βReviews β βProductsβ # βSubgraphβ βSubgraphβ βSubgraphβ βSubgraphβ # β β β β β β β β # β /users β β/orders β β/reviewsβ β/productsβ # β /auth β β /items β β /ratingβ β /stock β # ββββββββββ ββββββββββ ββββββββββ ββββββββββ # User DB Order DB Review DB Product DB
3. Membuat Subgraph
Setiap subgraph adalah GraphQL server independen yang mendefinisikan types dan resolvers untuk domain spesifik. Subgraph menggunakan federation directives untuk menandai types yang bisa di-share antar subgraphs.
Users Subgraph
# ===== users-subgraph/schema.graphql =====
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.7",
import: ["@key", "@shareable", "@external", "@requires"])
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
avatar: String
createdAt: String!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
me: User
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
}
input CreateUserInput {
name: String!
email: String!
}
input UpdateUserInput {
name: String
email: String
}
Users Subgraph Implementation (Node.js)
const { ApolloServer } = require('@apollo/server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const { readFileSync } = require('fs');
const gql = require('graphql-tag');
const typeDefs = gql(readFileSync('./schema.graphql', 'utf-8'));
const resolvers = {
Query: {
user: async (_, { id }) => {
return await db.users.findById(id);
},
users: async (_, { limit = 10, offset = 0 }) => {
return await db.users.findAll({ limit, offset });
},
me: async (_, __, { currentUser }) => {
return currentUser;
}
},
User: {
// __resolveReference: dipanggil oleh Router saat entity
// dari subgraph lain butuh data User
__resolveReference: async (reference) => {
// reference = { __typename: "User", id: "123" }
return await db.users.findById(reference.id);
},
// Field-level resolvers
createdAt: (user) => user.created_at.toISOString()
},
Mutation: {
createUser: async (_, { input }) => {
return await db.users.create(input);
},
updateUser: async (_, { id, input }) => {
return await db.users.update(id, input);
}
}
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers })
});
server.listen({ port: 4001 }).then(({ url }) => {
console.log(`Users subgraph running at ${url}`);
});
Orders Subgraph (extends User)
# ===== orders-subgraph/schema.graphql =====
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.7",
import: ["@key", "@shareable", "@external", "@requires"])
type Order @key(fields: "id") {
id: ID!
userId: ID!
items: [OrderItem!]!
total: Float!
status: OrderStatus!
createdAt: String!
}
type OrderItem {
productId: ID!
productName: String!
quantity: Int!
price: Float!
}
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
# EXTEND User type β tambahkan field orders ke User
# yang dimiliki oleh Users subgraph
type User @key(fields: "id") {
id: ID! @external
orders: [Order!]!
totalSpent: Float! @requires(fields: "id")
}
type Query {
order(id: ID!): Order
orders(userId: ID): [Order!]!
}
type Mutation {
createOrder(input: CreateOrderInput!): Order!
updateOrderStatus(id: ID!, status: OrderStatus!): Order!
}
input CreateOrderInput {
userId: ID!
items: [OrderItemInput!]!
}
input OrderItemInput {
productId: ID!
quantity: Int!
}
4. Entity Resolution & @key
Entity adalah type yang bisa di-referensikan dari subgraph lain. Di-tag dengan @key directive yang mendefinisikan field unik untuk identifikasi. Saat Router butuh data dari subgraph lain, ia mengirim representasi entity (typename + key fields) dan subgraph harus bisa me-resolve-nya.
# Entity Resolution Flow:
#
# Client query: { user(id:"123") { name, orders { total } } }
#
# Step 1: Router tahu User.name ada di Users subgraph
# Router tahu User.orders ada di Orders subgraph
#
# Step 2: Router kirim query ke Users subgraph:
# { user(id:"123") { id, name } }
#
# Step 3: Router kirim entity representations ke Orders subgraph:
# {
# _entities(representations: [
# { __typename: "User", id: "123" }
# ]) {
# ... on User { orders { total } }
# }
# }
#
# Step 4: Orders subgraph memanggil __resolveReference({ __typename: "User", id: "123" })
# yang mengembalikan data user + orders
#
# Step 5: Router meng-merge results dan mengirim ke client
# Multiple @key:
type Product @key(fields: "id") @key(fields: "sku") {
id: ID!
sku: String!
name: String!
price: Float!
}
# β Bisa di-resolve dengan "id" ATAU "sku"
5. Federation Directives
| Directive | Fungsi | Contoh |
|---|---|---|
@key(fields:) | Menandai type sebagai entity dengan primary key | type User @key(fields: "id") |
@external | Field dimiliki subgraph lain, di-extend di sini | id: ID! @external |
@requires(fields:) | Resolver butuh field external tertentu | totalSpent: Float @requires(fields: "id") |
@provides(fields:) | Subgraph bisa menyediakan field untuk entity | user: User @provides(fields: "name") |
@shareable | Field bisa di-resolve oleh multiple subgraphs | name: String @shareable |
@override(from:) | Move field ownership dari subgraph lain | email: String @override(from: "users") |
@inaccessible | Sembunyikan field dari supergraph | password: String @inaccessible |
@tag(name:) | Tag untuk contract testing / filtering | email: String @tag(name: "internal") |
6. Supergraph & Apollo Router
Supergraph adalah gabungan schema dari semua subgraphs yang di-compose oleh Apollo Rover CLI dan di-serve oleh Apollo Router.
# ===== router.yaml β Apollo Router Configuration =====
supergraph:
listen: 0.0.0.0:4000
introspection: true
# CORS configuration
cors:
allow_any_origin: false
origins:
- "https://app.example.com"
- "http://localhost:3000"
# Subgraph override URLs (untuk local dev)
override_subgraph_url:
users: http://localhost:4001/graphql
orders: http://localhost:4002/graphql
reviews: http://localhost:4003/graphql
products: http://localhost:4004/graphql
# Telemetry & monitoring
telemetry:
instrumentation:
spans:
mode: spec_compliant
exporters:
tracing:
otlp:
endpoint: http://jaeger:4317
protocol: grpc
# Query planning cache
supergraph.query_planning:
cache:
in_memory:
limit: 1000
# Traffic shaping
traffic_shaping:
router:
timeout: 30s
all:
timeout: 10s
subgraphs:
orders:
timeout: 15s # Orders butuh timeout lebih lama
reviews:
timeout: 5s
# Compose supergraph dari subgraph schemas rover supergraph compose \ --config ./supergraph-config.yaml \ > supergraph.graphql # Publish subgraph schema ke Apollo Studio rover subgraph publish my-graph@production \ --schema ./users-schema.graphql \ --name users \ --routing-url http://users-service:4001/graphql # Run Apollo Router locally docker run -p 4000:4000 \ --mount "type=bind,source=./router.yaml,target=/dist/config/router.yaml" \ ghcr.io/apollorouter/graph-router:latest \ --config /dist/config/router.yaml
7. Federation vs Schema Stitching
| Aspek | Apollo Federation | Schema Stitching |
|---|---|---|
| Approach | Decentralized β setiap tim kelola subgraph sendiri | Centralized β gateway menggabungkan schemas |
| Schema ownership | Per-team, autonomous | Gateway-level |
| Entity resolution | Built-in (@key, __resolveReference) | Manual stitching resolvers |
| Type merging | Automatic via federation directives | Manual configuration |
| Performance | Query planner optimized, parallel execution | Tergantung implementasi |
| Tooling | Apollo Studio, Rover CLI, Router | @graphql-tools/stitch |
| Learning curve | Medium β perlu paham directives | Medium-High β manual setup |
| Maturity | Industry standard (v2.7) | Cocok untuk migrasi gradual |
| Best for | Enterprise, multi-team, scale besar | Integrasi legacy GraphQL, simple needs |