🔷 GraphQL6. Federation02. Stitching → Federation 역사 — 2017 → 2019 → 2022

02. Stitching → Federation 역사 — 2017 → 2019 → 2022

GraphQL의 그래프 통합 진화는 세 세대를 거쳤다. 2017 schema stitching → 2019 Federation v1 → 2022 Federation v2. 각 단계는 이전 단계의 한계에서 출발했다 — 추상화의 흔한 패턴이다.


한 줄 답

Stitching(2017)은 런타임 합성이라 느리고 충돌 해석이 없었다. Federation v1(2019)은 컴파일타임 합성을 도입했지만 shared ownership 표현이 부족했다. Federation v2(2022)는 @shareable·@override팀 간 협업의 첫 클래스 시민을 만들었다.


Why — 왜 세대가 바뀌었나

GraphQL이 마이크로서비스 시대에 그래프 통합 문제를 풀어야 했던 건 불가피했다. 회사가 커지면 N개 GraphQL 서버가 생겼고, 클라이언트는 그것들이 한 그래프처럼 보이기를 원했다.

세 세대의 진화는 세 가지 다른 가정을 시험한 결과다.

세대시기합성 시점핵심 가정
Stitching2017런타임”gateway가 SDL을 가져와 합치면 된다”
Federation v12019컴파일 (rover compose)“subgraph가 명시적으로 통합 규칙을 선언해야 한다”
Federation v22022컴파일 + 합성 검증”팀 간 공유 소유권도 첫 클래스로 표현되어야 한다”

How — 세 세대의 동작 차이

1세대: Schema Stitching (2017~2019)

Apollo가 처음 만든 graphql-tools/stitching. 동작 방식:

// Gateway 코드
import { stitchSchemas } from '@graphql-tools/stitch';
 
const gateway = stitchSchemas({
  subschemas: [
    { schema: userSchema, executor: makeRemoteExecutor('http://user-svc/graphql') },
    { schema: orderSchema, executor: makeRemoteExecutor('http://order-svc/graphql') },
  ],
  typeDefs: `
    extend type User {
      orders: [Order!]!
    }
  `,
  resolvers: {
    User: {
      orders: (user, _, ctx, info) => delegateToSchema({
        schema: orderSchema,
        operation: 'query',
        fieldName: 'ordersByUserId',
        args: { userId: user.id },
        context: ctx, info,
      }),
    },
  },
});

→ Gateway가 런타임에 SDL을 가져와 합치고, 충돌은 gateway 코드에 직접 작성한 resolver가 해석한다.

문제점:

  1. 런타임 비용: gateway가 시작할 때 모든 subschema의 SDL을 fetch해야 함. 부팅이 느리다.
  2. 충돌 해석을 gateway 코드가 한다: 두 subschema에 동일한 type이 있으면 — gateway 개발자가 직접 transformSchema로 충돌을 풀어야 했다.
  3. 타입 안정성 부재: subschema가 말없이 schema를 바꿔도 gateway는 런타임에야 안다.
  4. 분산된 책임 → 중앙 집중: 결국 gateway 팀이 모든 통합 로직을 갖게 됨 → 다시 조직 병목.

→ stitching은 기술적으론 동작했지만 조직 문제를 풀지 못했다.

2세대: Federation v1 (2019)

Apollo가 반성하며 발표. 핵심 아이디어:

  • 컴파일타임 합성: subgraph 스키마들을 빌드 단계에서 supergraph SDL로 합성. 런타임 fetch 없음.
  • subgraph가 통합 규칙을 명시: gateway 코드가 아니라 subgraph SDL의 디렉티브통합 의도를 선언.
# user subgraph
type User @key(fields: "id") {
  id: ID!
  name: String!
}
 
# order subgraph — User를 *확장*
extend type User @key(fields: "id") {
  id: ID! @external
  orders: [Order!]!
}

extend type User가 *“이건 다른 subgraph가 정의한 User를 확장한다”*는 선언. gateway는 SDL을 읽기만 하고, 모든 통합 규칙은 subgraph가 소유한다.

v1의 한계:

  1. extend 필수: 같은 type을 동시에 두 subgraph에서 원래 정의할 수 없었다. 누군가는 주 소유자고 나머지는 extend.
  2. 공유 필드 표현 못함: e.g., User.emailUser 팀Identity 팀각자 정의하고 동일하다고 보장하는 케이스가 표현 불가.
  3. owner 이전이 위험: A팀이 정의한 필드를 B팀이 가져가려면 deploy를 정확히 동시에 해야 함 — federation은 원자적 마이그레이션을 지원 안 함.
  4. 합성 에러 메시지가 모호: 두 subgraph가 충돌해도 어느 쪽이 잘못인지 불분명.

3세대: Federation v2 (2022)

Apollo가 대대적 재설계. 핵심 변경:

(a) extend 키워드 제거 — 모든 subgraph가 동등

# user subgraph
type User @key(fields: "id") {
  id: ID!
  name: String!
}
 
# order subgraph — extend 안 써도 됨
type User @key(fields: "id") {
  id: ID!
  orders: [Order!]!
}

→ “이건 user subgraph 것”이라는 위계가 사라지고 — 모두가 User type에 기여하는 그림이 됐다.

(b) @shareable — 동일 필드를 여러 subgraph가 정의 가능

# user subgraph
type User @key(fields: "id") {
  id: ID!
  name: String @shareable
}
 
# identity subgraph
type User @key(fields: "id") {
  id: ID!
  name: String @shareable   # 같은 의미라고 명시적 합의
}

→ 두 팀이 같은 필드를 정의해도 명시적 동의 하에 합성된다.

(c) @override — 점진적 소유권 이전

# A subgraph — 원래 owner
type Product @key(fields: "id") {
  id: ID!
  price: Float!   # A팀 소유
}
 
# B subgraph — A로부터 가져옴
type Product @key(fields: "id") {
  id: ID!
  price: Float! @override(from: "A")   # 이제 B팀 것
}

→ B를 배포하는 순간 router는 B의 price를 우선한다. A는 나중에 안전하게 필드를 제거하면 된다.

(d) @inaccessible — 공개 스키마에서 숨김

type User @key(fields: "id") {
  id: ID!
  internalScore: Float @inaccessible   # supergraph 합성엔 있지만 클라이언트엔 안 보임
}

→ 내부 디버깅용 필드를 외부에 노출하지 않으면서 내부 router에선 쓸 수 있다.

(e) Composition 검증 강화

v2는 합성 시 충돌을 자세히 진단한다 — 어느 subgraph, 어느 필드, 왜 충돌인지.

COMPOSITION_ERROR: Field "User.email" is defined in both "user" and "identity"
  subgraphs but is not marked @shareable in "identity".
  Add @shareable to "identity", or remove the field from one subgraph.

대부분의 통합 문제를 컴파일 타임에 잡는다. stitching 시대엔 런타임에야 발견했던 것들.


What — 세대별 비교표

항목StitchingFederation v1Federation v2
합성 시점런타임컴파일 (rover compose)컴파일 + 강화된 검증
충돌 해석gateway 코드extend 위계디렉티브(@shareable/@override)
공유 필드resolver로 해결✗ 표현 불가@shareable
소유권 이전gateway 재배포 필요✗ 위험@override (점진적)
합성 에러런타임 ½ 미발견컴파일이지만 모호컴파일 + 정확한 진단
Gateway 구현Apollo ServerApollo Gateway (Node)Apollo Router (Rust)
권장 시점거의 없음 (legacy)신규 프로젝트 권장 안 함권장

What-if — 왜 지금 stitching을 쓰는 사람이 있나

케이스이유
Apollo 외 GraphQL 서버 통합Federation은 Apollo 친화적 SDL을 요구. Hasura, Postgraphile 등이 federation 호환 SDL을 부분만 지원
한 번에 GraphQL이 아닌 REST 통합Mesh(다음 챕터)가 stitching 기반
변경 빈도가 매우 낮음stitching의 런타임 비용이 드물게 일어나는 일이면 무시 가능
레거시 시스템 유지이미 stitching으로 만든 게 있고 굳이 마이그레이션 비용을 안 쓰는 경우

Apollo는 2020년 graphql-tools stitching을 deprecated하지 않았다 — 권장하지 않을 뿐이다. 즉, 여전히 일부 케이스에 정답이다.


Insight — 흥미로운 이야기

“Apollo가 federation을 공짜로 풀었던 이유”

Federation은 처음엔 Apollo Gateway(Node.js)만 무료였고 Apollo Studio(supergraph 관리 SaaS)가 유료였다. 2021년 Apollo는 Router를 Rust로 다시 짜고 완전 오픈소스로 풀었다. 이유: grpc로 도망가는 트래픽을 막아야 했고, competitor(Mesh, Hasura)에 표준을 뺏기지 않으려면 federation을 de facto 표준으로 만들어야 했다.

그 결정 덕에 federation이 오픈 스펙(specs.apollo.dev/federation)으로 풀렸고, Hot Chocolate Fusion(.NET) 같은 호환 구현이 나왔다.

“왜 v1 → v2 마이그레이션이 쉬웠는가

Apollo는 v2를 backward compatible하게 설계했다 — v1 subgraph는 그대로 v2 supergraph에 합성된다. 단, v2 디렉티브를 쓰려면 schema 상단에 *@link(url: "https://specs.apollo.dev/federation/v2.0", ...)*를 선언해야 한다.

opt-in 진화. 이건 GraphQL spec 전체의 철학이기도 하다.

“Schema stitching이 영원히 살아있는 이유”

Mesh, WunderGraph, Hasura의 remote schema 기능이 모두 stitching의 사상적 후예다. legacy 시스템 통합이라는 그래프-아닌 세계와의 다리 역할은 federation이 풀지 못한다.


요약 + Mermaid

Stitching(2017) → Federation v1(2019) → Federation v2(2022) 세 세대는 런타임 → 컴파일 → 협업으로 진화했다. 각 단계는 이전의 한계에서 출발했고, 조직 문제를 점점 더 잘 표현하게 됐다. v2가 지금의 사실상 표준이고, 대안은 7번 문서에서 본다.