🔷 GraphQL6. Federation05. @shareable & @override — 소유권 이전 도구

05. @shareable & @override — 소유권 이전 도구

Federation v1의 가장 큰 한계는 *“한 필드 = 한 owner”*였다. v2는 그 가정을 깨고 — 공유, 이전, 숨김을 SDL 디렉티브로 표현 가능하게 했다.

이 문서는 팀 간 협업의 SDL 어휘인 다섯 디렉티브를 다룬다.


한 줄 답

Federation v2의 협업 디렉티브 5개: ① @shareable — 두 subgraph가 같은 필드를 정의 ② @override — 필드 소유권을 점진적으로 이전@inaccessible — 공개 schema에서 숨김@external — 다른 subgraph의 필드를 참조만@requires/@provides — 필드 해석에 다른 필드가 필요함을 선언.


Why — 왜 5개가 필요한가

팀 간 협업의 자연스러운 패턴:

상황디렉티브
”A팀과 B팀이 같은 필드를 정의해야 함”@shareable
”A팀이 갖던 필드를 B팀이 가져가야 함@override
”내부에선 쓰지만 외부엔 안 보여야 함@inaccessible
”이 필드를 내가 아는데 해석은 다른 subgraph가 함”@external
”이 필드를 해석하려면 다른 필드가 미리 필요@requires/@provides

Federation v1엔 @external@requires/@provides만 있었다. v2는 공유와 이전을 추가해 팀 협업의 표면을 넓혔다.


How — 디렉티브별 상세

@shareable — 동등한 공유

# Inventory subgraph
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @shareable
}
 
# Catalog subgraph
type Product @key(fields: "id") {
  id: ID!
  inStock: Boolean! @shareable   # 둘 다 @shareable이어야 함
}

→ “이 필드는 두 subgraph가 각자 해석할 수 있다. 결과는 동등하다고 우리가 보증한다.” → router는 둘 중 하나를 골라 호출. 보통 해당 쿼리 path에 이미 도는 subgraph를 우선 (latency 최적화).

주의: 둘 다 @shareable반드시 있어야 함. 한쪽만 있으면 composition error. *“우연히 같은 필드를 둘이 정의”*를 막기 위한 안전장치.

언제 @shareable이 옳은가?

  • 같은 데이터를 두 subgraph가 자기 DB에서 직접 안다 — 예: 사용자 이메일을 User subgraph도 알고 Auth subgraph도 안다.
  • 한쪽이 cache 또는 read replica고 다른 쪽이 원본이지만 같은 결과를 보장 가능.

언제 @shareable위험한가?

두 subgraph가 진짜로 같은 결과를 반환하지 않을 때. e.g., 한쪽이 cache stale이면 — 클라이언트는 둘 중 어느 쪽을 받았는지 모른다. 결정성이 깨진다.

@override — 소유권 이전

가장 강력하고 가장 위험한 디렉티브.

# 이전: Inventory subgraph
type Product @key(fields: "id") {
  id: ID!
  price: Float!   # Inventory 팀 소유
}
 
# 이후: Pricing subgraph (새로 만든 팀)
type Product @key(fields: "id") {
  id: ID!
  price: Float! @override(from: "inventory")   # Pricing 팀이 가져감
}

→ Pricing이 publish되는 그 순간부터 router는 Pricing에게 price를 묻는다. Inventory는 여전히 price를 정의하고 있어도 호출되지 않는다.

→ 이게 점진적 마이그레이션의 정체:

  1. Pricing이 price를 정의하고 @override(from: "inventory") 붙여서 publish.
  2. Router는 즉시 Pricing으로 트래픽 이전.
  3. 문제 없으면 Inventory에서 price 필드를 제거하고 publish.
  4. 마이그레이션 완료. 단 한 번도 downtime이나 충돌 없음.

@overrideProgressive 모드 (v2.7+)

type Product @key(fields: "id") {
  price: Float! @override(from: "inventory", label: "percent(25)")
}

→ 25%의 트래픽만 Pricing으로, 75%는 Inventory로. 카나리 배포필드 레벨에서.

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

type User @key(fields: "id") {
  id: ID!
  name: String!
  internalScore: Float @inaccessible   # supergraph에는 있지만 클라이언트 스키마에는 없음
  passwordHash: String @inaccessible
}

→ supergraph SDL에는 합성되어 존재. router도 내부적으로 해석 가능. 하지만 클라이언트가 보는 스키마(API)에서는 사라짐. introspection도 안 됨.

언제 쓰나?

  1. 점진적 deprecation: 필드를 바로 삭제하지 않고 외부 노출만 차단 → 클라이언트 코드가 그것을 못 쓰게 하면 안전하게 삭제 가능.
  2. 내부 디버깅 필드: 운영 대시보드는 보지만 외부 API에서는 안 보이게.
  3. 임시 필드: 다른 subgraph로 옮길 동안 잠깐 숨김.

@external — 참조만

# Review subgraph
type Product @key(fields: "id") {
  id: ID!
  reviews: [Review!]!
  averageRating: Float!
}

v1에선 Review가 Product의 id 외 다른 필드를 참조하려면 @external이 필요했다 — “이 필드는 내가 정의하는 게 아니라 다른 subgraph 것이야”.

v2에서는 @external대부분 자동이라 명시적으로 쓸 일이 줄었다. 단, @requires/@provides와 함께는 여전히 필요.

@requires — 필드 해석에 다른 필드가 필요

# Shipping subgraph
type Product @key(fields: "id") {
  id: ID!
  shippingCost(zipCode: String!): Float! @requires(fields: "weight dimensions { length width height }")
}
 
# 다른 subgraph가 정의한 weight, dimensions를 *Shipping이 필요*로 함
extend type Product @key(fields: "id") {
  weight: Float! @external
  dimensions: Dimensions! @external
}

→ router는 shippingCost를 해석하기 전에 반드시 weight와 dimensions를 먼저 가져옴. 그래야 shippingCost를 계산할 수 있으니까.

@provides — 다른 subgraph 필드를 함께 제공

# Review subgraph
type Review {
  id: ID!
  body: String!
  product: Product @provides(fields: "name")
}
 
type Product @key(fields: "id") {
  id: ID!
  name: String! @external   # 본래는 Product subgraph 것
}

→ Review subgraph가 reviews를 가져올 때 Product.name도 함께 반환할 수 있음. 그러면 router는 name을 위해 Product subgraph로 다시 갈 필요 없음 → 성능 최적화.

→ 활용 케이스: Review가 denormalized Product 정보를 이미 캐시하고 있을 때.


What — 디렉티브 결정 트리

질문 1) 두 subgraph가 같은 필드 정의?
  ├─ 정의 의도가 *동일*함 → @shareable 양쪽 (안전)
  ├─ 한쪽이 *덮어쓰려 함* → @override(from: "X")
  └─ 우연한 충돌 → composition error (둘 중 하나 삭제)

질문 2) 필드 노출은 안 하고 싶은데?
  ├─ supergraph에는 두되 외부 hidden → @inaccessible
  └─ 완전히 제거 → schema에서 삭제

질문 3) 다른 subgraph의 필드를 참조만 함?
  └─ @external (대개 v2에서는 암묵적)

질문 4) 이 필드 해석에 다른 필드 필요?
  ├─ 같은 entity의 다른 필드 → @requires
  └─ 함께 반환할 수 있음 → @provides

What-if — 흔한 함정

함정증상원인해결
@shareable 한쪽만 붙임composition error: “field defined in both”v2는 명시적 합의 요구양쪽 모두 @shareable
@override로 가져갔는데 원 owner도 필드 유지latency 낭비 (router는 안 부르지만)마이그레이션 미완안정되면 원 owner subgraph에서 제거
@inaccessible인데 내부 쿼리에서 못 가져옴”Cannot query field”router는 공개 스키마 기준으로 검증inaccessible은 외부 차단. 내부 호출도 정의 위치 subgraph 직접 호출이 필요
@requires의 의존 필드가 다른 subgraph 것”Cannot resolve external field”@external 같이 선언 안 함extend 안에서 @external 함께
Progressive override의 라벨이 런타임에 결정 안 됨100%만 override 됨router가 label feature 모름Router v1.45+ 필요

진짜 사례: 잘못된 @shareable로 인한 결정성 깨짐

# A subgraph (실시간 DB)
type Product @key(fields: "id") {
  inStock: Boolean! @shareable   # 실시간 재고
}
 
# B subgraph (5분 cache)
type Product @key(fields: "id") {
  inStock: Boolean! @shareable   # 5분 stale
}

→ 같은 쿼리가 어떤 path냐에 따라 다른 답을 받는다. client는 어떤 진실을 봤는지 모름. 이건 @shareable의 안티패턴. cache subgraph는 별도 필드명(e.g., inStockCached)을 쓰거나 @shareable을 빼야 한다.


Insight — 흥미로운 이야기

“@override는 콜드 마이그레이션의 종말”

전통적 마이크로서비스 마이그레이션은 콜드 컷오버였다 — A 서비스를 멈추고 B로 트래픽을 옮기고 다시 켬. downtime 또는 복잡한 dual-write. @override는 그 둘을 SDL 한 줄로 대체한다. 데이터 평면 변경 없이 그래프 평면에서 옮긴다.

→ “마이그레이션은 코드가 아니라 선언이다”라는 패러다임 전환.

“@inaccessible은 Schema Registry의 governance hook

대기업에서 @inaccessible보안 감사와 함께 쓰인다. e.g., GDPR 대상 필드는 기본 inaccessible이고 명시적 승인을 받아야 노출. Schema lifecycle의 거버넌스가 디렉티브 한 줄로 끼어든다.

“Apollo는 왜 @transform 같은 변환 디렉티브를 안 만들었나”

다른 federation 구현(예: WunderGraph)은 필드 변환 디렉티브를 제공한다. Apollo는 명시적으로 거부했다. 이유: 변환은 subgraph가 자기 책임으로 해야 한다 — federation은 통합 표면이지 변환 엔진이 아니다. 책임 경계의 선언.

“@override의 라벨 모드카오스 엔지니어링과 만난다

Progressive override가 25%/75% split을 지원하기 시작하면서 — 일부 회사는 카오스 실험에 쓴다. e.g., Pricing 새 subgraph로 5%만 보내고 latency 비교. 필드 단위 카나리가 federation의 디렉티브 한 줄로 가능해진다.


요약 + Mermaid

Federation v2의 협업 디렉티브 5개는 팀 간 협업의 SDL 어휘다. @shareable(공유) · @override(이전) · @inaccessible(숨김) · @external(참조) · @requires/provides(의존). 이 어휘로 조직의 변화그래프의 변화로 표현한다 — 수평적 협업수직적 마이그레이션을 모두 SDL이 담는다.