TL;DR
- **Federation**: 여러 GraphQL 서비스를 단일 supergraph로 통합. 마이크로서비스 환경의 표준
- **Schema Stitching의 후계자**: Federation v2가 stitching을 대체. 더 강력하고 명확
- **Subgraph 설계**: 도메인 단위로 분할. Entity로 subgraph 간 데이터 공유
- **Apollo Router**: Rust로 작성. Gateway보다 10배 빠름. CNCF 표준
- **Netflix, Airbnb, Expedia, GitHub** 모두 Federation 채택
1. 왜 GraphQL Federation이 필요한가?
1.1 모놀리식 GraphQL의 한계
[클라이언트]
↓
[GraphQL Server]
↓
[User Service] [Order Service] [Product Service] [...]
**문제점**:
1. **단일 팀 병목**: GraphQL 스키마가 한 곳에서 관리됨
2. **거대한 코드베이스**: 모든 도메인이 한 서버
3. **배포 결합**: 한 도메인 변경 = 전체 재배포
4. **확장성**: 단일 장애점
1.2 Schema Stitching (이전 시도)
[Stitching Gateway]
├─ User Schema
├─ Order Schema
└─ Product Schema
여러 GraphQL 스키마를 합쳐 하나로 제공.
**한계**:
- 수동 충돌 해결
- 복잡한 설정
- 성능 문제
- 명확한 계약 부족
1.3 Federation의 등장
**Apollo Federation v1** (2019): Schema stitching의 진화.
**Federation v2** (2022): 더 명확한 디자인, 더 강력.
[Apollo Router]
/ | \
[Users] [Orders] [Products] ← 각 subgraph가 독립
**핵심**: 각 팀이 자기 도메인의 GraphQL 서비스 운영. Router가 자동 합성.
2. Federation 핵심 개념
2.1 Subgraph
**Subgraph** = 페더레이션의 단위. 자체 GraphQL 스키마.
users-subgraph
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
**`@key`**: 이 타입을 다른 subgraph에서 참조 가능하게 만듦.
2.2 Entity
**Entity** = 여러 subgraph 간 공유되는 타입. `@key`로 표시.
**users-subgraph**:
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
**orders-subgraph**:
type Order @key(fields: "id") {
id: ID!
total: Float!
user: User! # User entity 참조
}
User를 확장 (extend가 v1, v2에서는 그냥 정의)
type User @key(fields: "id") {
id: ID! @external
orders: [Order!]! # 새 필드 추가
}
→ **같은 User 타입에 두 subgraph가 다른 필드 제공**.
2.3 Reference Resolver
다른 subgraph가 entity를 참조할 때, 해당 entity의 데이터를 가져오는 방법:
// users-subgraph
const resolvers = {
User: {
__resolveReference(reference) {
// reference = { id: "123" }
return getUserById(reference.id)
}
}
}
Router가 `{ id: "123" }`만 보내면, users-subgraph가 전체 User 데이터를 반환.
2.4 Composition
Router는 모든 subgraph 스키마를 받아 **supergraph schema**를 생성.
Subgraph A schema ─┐
Subgraph B schema ─┼→ [Composition] → Supergraph schema
Subgraph C schema ─┘
클라이언트는 단일 supergraph 스키마만 봅니다.
3. 실전 예시 — 전자상거래
3.1 Subgraph 설계
**3개의 subgraph**:
**1. Users**
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
address: Address!
}
type Address {
street: String!
city: String!
zipcode: String!
}
type Query {
user(id: ID!): User
me: User
}
**2. Products**
type Product @key(fields: "id") {
id: ID!
name: String!
description: String!
price: Float!
stock: Int!
}
type Query {
product(id: ID!): Product
products(category: String): [Product!]!
}
**3. Reviews**
type Review @key(fields: "id") {
id: ID!
rating: Int!
comment: String!
productId: ID!
authorId: ID!
}
Product 확장
type Product @key(fields: "id") {
id: ID! @external
reviews: [Review!]!
averageRating: Float!
}
User 확장
type User @key(fields: "id") {
id: ID! @external
reviews: [Review!]!
}
3.2 클라이언트 쿼리
query {
user(id: "123") {
name # users-subgraph
email # users-subgraph
reviews { # reviews-subgraph
rating
comment
product { # products-subgraph
name
price
}
}
}
}
**Router가 자동으로**:
1. users-subgraph: `name`, `email` 가져옴
2. reviews-subgraph: User의 reviews 가져옴
3. products-subgraph: 각 review의 product 정보 가져옴
클라이언트는 **단일 쿼리**, 단일 응답을 받습니다.
3.3 실행 흐름
[Client] → [Router]
↓
├─ users-subgraph: query user(id: 123)
│ ↓
│ { name, email, reviews: ??? }
│
├─ reviews-subgraph:
│ __resolveReference: User { id: 123 }
│ → return reviews
│
└─ products-subgraph:
__resolveReference: Product { id: 456 }
→ return product details
[Router] → [Composes response] → [Client]
4. Apollo Router — 새로운 표준
4.1 Apollo Gateway → Router 마이그레이션
**Apollo Gateway** (Node.js, 2019):
- Federation의 첫 구현
- Node.js 기반
- 단일 스레드, 메모리 사용 많음
**Apollo Router** (Rust, 2022):
- Rust로 다시 작성
- **10배 이상 빠름**
- 메모리 효율
- 더 적은 인스턴스로 같은 트래픽 처리
4.2 Router 설치
다운로드
curl -sSL https://router.apollo.dev/download/nix/latest | sh
실행
./router --config router.yaml --supergraph supergraph.graphql
**`router.yaml`**:
supergraph:
listen: 0.0.0.0:4000
cors:
origins:
- https://example.com
telemetry:
metrics:
prometheus:
enabled: true
tracing:
otlp:
enabled: true
endpoint: http://otel-collector:4317
4.3 Schema Composition
각 subgraph 변경 시 supergraph 재생성:
Rover CLI
rover supergraph compose --config supergraph-config.yaml > supergraph.graphql
**`supergraph-config.yaml`**:
federation_version: 2
subgraphs:
users:
routing_url: http://users-service:4001
schema:
subgraph_url: http://users-service:4001
products:
routing_url: http://products-service:4002
schema:
subgraph_url: http://products-service:4002
reviews:
routing_url: http://reviews-service:4003
schema:
subgraph_url: http://reviews-service:4003
4.4 GraphOS — Apollo의 매니지드
**Apollo Studio / GraphOS**:
- Schema registry (자동 composition)
- Schema validation (CI 통합)
- Operation 분석
- Performance 모니터링
- Schema diff (브레이킹 체인지 감지)
CI에서 schema validation
rover subgraph check my-graph@prod \
--schema users.graphql \
--name users
5. Federation 디렉티브
5.1 `@key`
Entity 정의:
type User @key(fields: "id") {
id: ID!
name: String!
}
복합 키
type Order @key(fields: "id userId") {
id: ID!
userId: ID!
total: Float!
}
다중 key
type Product
@key(fields: "id")
@key(fields: "sku") {
id: ID!
sku: String!
name: String!
}
5.2 `@external`
다른 subgraph가 정의한 필드를 참조:
type User @key(fields: "id") {
id: ID! @external
reviews: [Review!]! # 새 필드
}
5.3 `@requires`
다른 subgraph의 필드가 필요:
type Product @key(fields: "id") {
id: ID! @external
weight: Float! @external
shippingCost: Float! @requires(fields: "weight")
}
`shippingCost`를 계산하려면 `weight`가 필요. Router가 자동으로 weight를 가져와 전달.
5.4 `@provides`
이 subgraph가 제공할 수 있는 필드:
type Review @key(fields: "id") {
id: ID!
product: Product! @provides(fields: "name")
}
type Product @key(fields: "id") {
id: ID! @external
name: String! @external
}
→ Reviews subgraph가 product name까지 직접 반환 → product subgraph 호출 절약.
5.5 `@shareable` (v2)
여러 subgraph가 같은 필드를 정의:
users-subgraph
type User @key(fields: "id") {
id: ID!
name: String! @shareable
}
auth-subgraph
type User @key(fields: "id") {
id: ID!
name: String! @shareable # 같은 필드, 둘 다 정의 가능
}
5.6 `@inaccessible`
특정 필드를 supergraph에서 숨김:
type User @key(fields: "id") {
id: ID!
name: String!
internalNotes: String! @inaccessible # supergraph에 노출 안 됨
}
6. 흔한 패턴
6.1 도메인 분할
**잘못된 분할**: 기술별
- Database subgraph
- Cache subgraph
- Auth subgraph
**올바른 분할**: 비즈니스 도메인별
- Users subgraph
- Orders subgraph
- Products subgraph
- Inventory subgraph
- Shipping subgraph
→ 각 팀이 자기 도메인 책임.
6.2 Entity 소유권
각 entity는 **하나의 "owner" subgraph**가 있어야 합니다.
**소유**: 해당 entity의 정체성을 정의하는 곳.
User entity → users-subgraph가 소유
- id, name, email (소유 필드)
- orders-subgraph: User에 orders 필드 추가
- reviews-subgraph: User에 reviews 필드 추가
6.3 N+1 방지
query {
users {
name
orders {
id
}
}
}
**잘못된 구현**: 각 user마다 별도 호출 → N+1.
**해결**: **DataLoader** 패턴.
const orderLoader = new DataLoader(async (userIds) => {
// 한 번의 쿼리로 여러 user의 orders 가져옴
const orders = await db.query('SELECT * FROM orders WHERE user_id IN (?)', userIds)
return userIds.map(id => orders.filter(o => o.user_id === id))
})
const resolvers = {
User: {
orders: (user) => orderLoader.load(user.id)
}
}
6.4 인증/인가
**Router 레벨**:
authentication:
router:
jwt:
jwks:
- url: https://auth.example.com/.well-known/jwks.json
**Subgraph로 컨텍스트 전달**:
headers:
all:
request:
- propagate:
named: authorization
→ Router가 JWT를 검증하고 subgraph로 전달.
7. 운영과 모니터링
7.1 메트릭
**Apollo Router는 Prometheus 메트릭** 기본 지원:
apollo_router_http_requests_total
apollo_router_http_request_duration_seconds
apollo_router_operations_total
apollo_router_session_count
**대시보드**: Grafana로 시각화.
7.2 분산 트레이싱
telemetry:
tracing:
otlp:
enabled: true
endpoint: http://otel-collector:4317
propagation:
trace_context: true
jaeger: true
→ Router → Subgraph 호출이 같은 trace에 포함.
**Jaeger UI**:
[Router] (50ms)
├─ [users-subgraph] (10ms)
├─ [orders-subgraph] (30ms)
│ └─ [DB query] (25ms) ← 병목
└─ [Composition] (10ms)
7.3 Schema Registry
**왜 필요한가?**: Subgraph 변경이 다른 subgraph를 망가뜨릴 수 있음.
CI에서 변경 검증
rover subgraph check my-graph@prod \
--schema new-schema.graphql \
--name users
**검출**:
- Breaking change (필드 제거)
- Composition 충돌 (다른 subgraph와 모순)
- 사용량 경고
7.4 Persisted Queries
**보안 + 성능**:
- 클라이언트가 쿼리 ID만 전송
- Router는 미리 등록된 쿼리만 실행
// Persisted query 등록
const queryId = "abc123"
const query = "query GetUser($id: ID!) { user(id: $id) { name } }"
// 클라이언트
fetch('/graphql', {
body: JSON.stringify({
extensions: {
persistedQuery: {
version: 1,
sha256Hash: queryId
}
},
variables: { id: "123" }
})
})
**효과**:
- 임의 쿼리 차단 (보안)
- 작은 페이로드 (네트워크 절약)
- 사전 분석 (성능 모니터링)
8. 마이그레이션 전략
8.1 모놀리식 GraphQL → Federation
**1단계: Schema 분석**
기존 모놀리스
type Query {
user(id: ID!): User
product(id: ID!): Product
order(id: ID!): Order
}
도메인별로 분류.
**2단계: 첫 subgraph 추출**
- 가장 명확한 도메인부터 (예: users)
- 모놀리스는 그대로 두고 새 subgraph 추가
- Router를 모놀리스 앞에
**3단계: 점진적 분리**
- 한 도메인씩 모놀리스에서 추출
- 모놀리스의 해당 부분 제거
**4단계: 모놀리스 제거**
- 모든 도메인이 분리되면 모놀리스 폐기
8.2 REST → Federation
REST API를 GraphQL로 감싸기:
**Step 1**: 각 REST API를 GraphQL subgraph로 래핑
const resolvers = {
Query: {
user: async (_, { id }) => {
const response = await fetch(`http://users-rest/users/${id}`)
return response.json()
}
}
}
**Step 2**: 여러 wrapper subgraph를 federate
**Step 3**: 점진적으로 native GraphQL로 전환
8.3 일반적 함정
**1. 너무 작게 분할**
- 5명짜리 도메인을 10개 subgraph로 → 운영 지옥
- **Conway's Law**: 팀 구조에 맞추기
**2. Entity ownership 모호**
- 두 subgraph가 같은 entity 소유 주장 → 충돌
- 명확한 owner 결정
**3. 동기 의존성**
- A subgraph가 B에 동기적으로 의존 → 캐스케이드 실패
- DataLoader, 캐싱
9. 실제 사례
9.1 Netflix
- **수백 개 subgraph**
- Java + DGS (Domain Graph Service) framework
- Schema-first 접근
- Backend for Frontend (BFF) 패턴
9.2 Airbnb
- 모놀리스 → Federation 마이그레이션
- 중요한 발견: **점진적 마이그레이션**의 중요성
- 한 번에 하지 말 것
9.3 Expedia
- 여행 검색의 거대 시스템
- 50+ subgraph
- DataLoader 패턴 광범위 사용
9.4 GitHub
- Public GraphQL API (federation 사용)
- 외부 개발자 친화적
10. Federation vs 대안
10.1 GraphQL 모놀리스 vs Federation
| | 모놀리스 | Federation |
|---|---|---|
| **복잡도** | 낮음 | 높음 |
| **팀 자율성** | 낮음 | 높음 |
| **확장성** | 단일 노드 | 무한 |
| **개발 속도** | 작은 팀 빠름 | 큰 팀 빠름 |
| **운영** | 단순 | 복잡 |
**선택**: 팀 5명 미만 → 모놀리스. 그 이상 → Federation 고려.
10.2 Federation vs BFF
**BFF (Backend for Frontend)**:
- 클라이언트별 GraphQL 서버 (web, mobile, partner)
- 각 BFF가 여러 서비스 호출
**Federation**:
- 단일 supergraph
- 클라이언트 무관
**조합**: Federation + 클라이언트별 view (Apollo client preset).
10.3 Federation vs gRPC
**gRPC**: 강한 타입, 빠름. 하지만 클라이언트(특히 web)가 직접 사용 어려움.
**Federation**: GraphQL 유연성. 내부적으로 gRPC 가능.
**일반 패턴**:
- 클라이언트 ↔ Apollo Router (GraphQL)
- Router ↔ Subgraph (GraphQL)
- Subgraph ↔ 내부 서비스 (gRPC)
퀴즈
**답**: **Schema Stitching** (이전): 여러 GraphQL 스키마를 합쳐 하나로 제공. 수동 충돌 해결, 복잡한 설정, 명확한 계약 부족. **Federation** (Apollo): subgraph 간 명시적 계약 (`@key`, `@external`). Composition이 자동, breaking change 자동 검출. **Federation v2**가 stitching의 모든 단점을 해결하여 사실상 표준이 되었습니다. Stitching은 deprecated.
**답**: **Entity를 정의**합니다. `@key(fields: "id")`는 "이 타입은 `id` 필드로 식별 가능하며, 다른 subgraph에서 참조 가능"을 의미. Router가 entity reference를 만날 때 `id`로 해당 subgraph에 데이터를 요청합니다 (`__resolveReference`). 복합 key (`@key(fields: "id userId")`) 또는 다중 key도 가능. **Federation의 가장 중요한 디렉티브**.
**답**: **Rust로 다시 작성**되었기 때문입니다. Apollo Gateway는 Node.js 기반 (단일 스레드, 메모리 많이 사용). Apollo Router는 (1) Rust의 zero-cost abstraction, (2) async runtime (tokio)로 진정한 멀티스레드, (3) 메모리 효율. 결과: **10배 이상 빠르고, 메모리 절반 이하**. 같은 트래픽에 더 적은 인스턴스. Apollo Gateway는 deprecated.
**답**: **비즈니스 도메인별**로 분할합니다. 잘못된 예: Database, Cache, Auth (기술별). 올바른 예: Users, Orders, Products, Inventory, Shipping (도메인별). **Conway's Law**를 따라 팀 구조와 일치시키는 것이 중요. 한 팀이 한 subgraph를 책임지면 자율성 + 빠른 개발. 너무 작게 분할(5명짜리 도메인 → 10개)하지 말고, 너무 크게 분할(50명짜리 단일 subgraph)하지도 말 것.
**답**: **DataLoader 패턴**을 사용합니다. 같은 tick에 여러 entity 요청을 모아 한 번의 쿼리로 처리. Router는 entity reference를 batch로 보내고, subgraph는 DataLoader로 한 번에 처리. 또한 (1) `@provides` 디렉티브로 cross-subgraph 호출 자체를 줄이거나, (2) 캐싱 (Redis), (3) GraphQL operation 분석으로 자주 함께 요청되는 entity를 식별하여 최적화. **DataLoader는 GraphQL의 필수 패턴**.
참고 자료
- [Apollo Federation Docs](https://www.apollographql.com/docs/federation/)
- [Apollo Router](https://www.apollographql.com/docs/router/)
- [GraphOS](https://www.apollographql.com/graphos)
- [Federation v2 Spec](https://www.apollographql.com/docs/federation/federation-spec/)
- [DataLoader](https://github.com/graphql/dataloader)
- [Netflix DGS](https://netflix.github.io/dgs/) — Java framework
- [Hot Chocolate](https://chillicream.com/docs/hotchocolate) — .NET GraphQL with federation
- [Mercurius](https://mercurius.dev/) — Node.js with federation
- [GraphQL Schema Design](https://principledgraphql.com/) — Apollo's design principles
- [How Netflix Scales GraphQL](https://netflixtechblog.com/how-netflix-scales-its-api-with-graphql-federation-part-1-ae3557c187e2)
- [Airbnb GraphQL Migration](https://medium.com/airbnb-engineering/our-journey-into-graphql-federation-fe1c4f73ad1c)
현재 단락 (1/447)
- **Federation**: 여러 GraphQL 서비스를 단일 supergraph로 통합. 마이크로서비스 환경의 표준