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를 정의하고 있어도 호출되지 않는다.
→ 이게 점진적 마이그레이션의 정체:
- Pricing이 price를 정의하고
@override(from: "inventory")붙여서 publish. - Router는 즉시 Pricing으로 트래픽 이전.
- 문제 없으면 Inventory에서 price 필드를 제거하고 publish.
- 마이그레이션 완료. 단 한 번도 downtime이나 충돌 없음.
@override의 Progressive 모드 (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도 안 됨.
언제 쓰나?
- 점진적 deprecation: 필드를 바로 삭제하지 않고 외부 노출만 차단 → 클라이언트 코드가 그것을 못 쓰게 하면 안전하게 삭제 가능.
- 내부 디버깅 필드: 운영 대시보드는 보지만 외부 API에서는 안 보이게.
- 임시 필드: 다른 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
└─ 함께 반환할 수 있음 → @providesWhat-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이 담는다.