Skip to content

필사 모드: GraphQL Federation 완전 가이드 2025: Apollo Federation, Supergraph, 분산 GraphQL

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

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로 통합. 마이크로서비스 환경의 표준

작성 글자: 0원문 글자: 11,268작성 단락: 0/447