GraphQL Federation은 여러 팀이 독립적으로 소유한 도메인 스키마를 하나의 Supergraph로 안전하게 결합해 단일 GraphQL 엔드포인트처럼 제공하는 아키텍처입니다. React 앱에서는 단일 클라이언트에서 다양한 백엔드 도메인을 유연하게 합성하고, 점진적 확장과 성능 최적화를 동시에 달성할 수 있습니다.
1. Federation이 React 프런트엔드에 주는 이점
단일 쿼리로 다양한 도메인의 데이터를 연결해 화면을 그릴 수 있습니다. 팀 별 독립 배포가 가능하며, 스키마 충돌은 컴포지션 단계에서 조기에 검출합니다. 클라이언트는 엔드포인트를 하나만 알면 되므로 라우팅과 캐싱 전략 수립이 단순해집니다.
2. 핵심 용어 정리: Supergraph, Subgraph, Gateway/Router, Entity
Supergraph는 전체 그래프의 합성 결과이며, Subgraph는 각 도메인 팀이 제공하는 부분 그래프입니다. Gateway/Router는 Subgraph 스키마를 컴포즈해 쿼리 플랜을 만들고, 서브그래프로 라우팅합니다. Entity는 @key 지시어로 식별 가능한 타입으로, 서로 다른 서브그래프가 동일 엔티티를 확장할 수 있습니다.
3. 최소 예제: 두 개의 Subgraph 구성
Products와 Reviews 두 서브그래프를 구성해, Product 엔티티를 중심으로 리뷰를 합성합니다. 아래 예시는 Apollo Subgraph 서버를 사용합니다.
// products/index.mjs
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = `#graphql
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@shareable"])
type Product @key(fields: "id") {
id: ID!
sku: String!
name: String!
}
type Query {
product(id: ID!): Product
}
`;
const products = [
{ id: '1', sku: 'SKU-1', name: 'Keyboard' },
{ id: '2', sku: 'SKU-2', name: 'Mouse' }
];
const resolvers = {
Query: {
product: (_, { id }) => products.find(p => p.id === id)
},
Product: {
__resolveReference: (ref) => products.find(p => p.id === ref.id)
}
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers })
});
const { url } = await startStandaloneServer(server, { listen: { port: 4001 } });
console.log(`products subgraph ready at ${url}`);
리뷰 서브그래프에서는 Product를 확장하고, User 엔티티를 함께 노출합니다.
// reviews/index.mjs
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = `#graphql
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@external"])
type Review {
id: ID!
body: String!
author: User!
productId: ID!
}
type User @key(fields: "id") {
id: ID!
name: String!
}
extend type Product @key(fields: "id") {
id: ID! @external
reviews: [Review!]!
}
type Query {
_health: String
}
`;
const users = [
{ id: 'u1', name: 'Alice' },
{ id: 'u2', name: 'Bob' }
];
const reviews = [
{ id: 'r1', body: 'Great!', authorId: 'u1', productId: '1' },
{ id: 'r2', body: 'So so', authorId: 'u2', productId: '1' }
];
const resolvers = {
Product: {
reviews: (product) => reviews.filter(r => r.productId === product.id)
},
Review: {
author: (review) => users.find(u => u.id === review.authorId)
},
User: {
__resolveReference: (ref) => users.find(u => u.id === ref.id)
}
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers })
});
const { url } = await startStandaloneServer(server, { listen: { port: 4002 } });
console.log(`reviews subgraph ready at ${url}`);
4. Gateway로 Supergraph 실행
개발 단계에서는 Apollo Gateway의 IntrospectAndCompose로 빠르게 조합합니다. 운영에서는 정적 Supergraph SDL을 사용하거나 Apollo Router를 권장합니다.
// gateway/index.mjs
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'products', url: 'http://localhost:4001/graphql' },
{ name: 'reviews', url: 'http://localhost:4002/graphql' }
]
})
});
const server = new ApolloServer({ gateway });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`gateway ready at ${url}`);
이제 http://localhost:4000/graphql 하나의 엔드포인트로 통합 쿼리가 가능합니다.
5. React 클라이언트 설정(Apollo Client)
React에서는 Apollo Client를 사용해 단일 엔드포인트를 호출합니다. Persisted Query와 GET 사용을 적용해 CDN 캐시와 네트워크 효율을 높입니다.
// src/apollo.ts
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { ApolloProvider, gql, useQuery } from '@apollo/client';
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
import { setContext } from '@apollo/client/link/context';
import { sha256 } from 'crypto-hash';
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
};
});
const pqLink = createPersistedQueryLink({ sha256, useGETForHashedQueries: true });
const httpLink = new HttpLink({ uri: '/graphql', credentials: 'include' });
export const client = new ApolloClient({
link: authLink.concat(pqLink).concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Product: { keyFields: ['id'] },
User: { keyFields: ['id'] }
}
})
});
export const PRODUCT_PAGE = gql`
query ProductPage($id: ID!) {
product(id: $id) {
id
name
reviews {
id
body
author { id name }
}
}
}
`;
export function ProductPage({ id }) {
const { data, loading, error } = useQuery(PRODUCT_PAGE, { variables: { id } });
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
const p = data.product;
return (
<div>
<h2>{p.name}</h2>
{p.reviews.map(r => (
<div key={r.id}>{r.body} - {r.author.name}</div>
))}
</div>
);
}
Gateway 하나만 바라보므로, 라우팅·리트라이·에러 핸들링 로직을 단순화할 수 있습니다. Federation에서는 __typename과 keyFields가 일관되게 유지되므로 캐시 정합성이 좋습니다.
6. Federation에서의 캐시와 타입 정책
엔티티의 keyFields를 Federation의 @key 정의와 일치시키는 것이 중요합니다. 동일 엔티티가 여러 서브그래프에서 확장되어도 Apollo Client는 동일 캐시 엔트리로 병합합니다. Fragment를 적극 사용해 화면 단위 데이터 의존성을 캡슐화하면 변경 영향 범위를 줄일 수 있습니다.
7. 성능 최적화 팁
HTTP/2 연결 재사용과 요청 배칭, Persisted Query + GET으로 CDN 캐시 활용을 권장합니다. 부분 렌더링이 필요한 경우 @defer/@stream을 고려합니다. Apollo Router 사용 시 Federation에서의 부분 전송을 지원해 TTFB를 개선할 수 있습니다. 또한 응답 사이즈를 줄이기 위해 꼭 필요한 필드만 요청하는 쿼리 위생을 유지합니다.
8. 타입 안전과 코드 생성
GraphQL Code Generator로 타입을 생성하면 클라이언트·서버 간 계약을 강제할 수 있습니다. Federation 환경에서도 Supergraph SDL을 기준으로 코드 생성하거나, 클라이언트가 사용하는 쿼리 문서 기반으로 타입을 생성해 안전성을 확보합니다.
9. 인증/보안 전략
Gateway에서 인증을 수행하고, 사용자 컨텍스트를 하위 서브그래프로 전달합니다. 세분화된 권한 검사는 각 서브그래프에서 도메인 규칙에 맞게 처리합니다. 토큰 회전, 헤더 전달, 데이터 마스킹 정책은 Supergraph 레벨에서 일관되게 정의하는 것이 운영에 유리합니다.
10. 점진적 마이그레이션 전략
기존 단일 GraphQL 서버가 있다면 도메인 단위로 Subgraph를 분리해가며 Federation으로 옮깁니다. REST에서 전환할 경우, BFF 성격의 Subgraph를 먼저 만들고 점차 도메인 팀 소유의 Subgraph로 이관합니다. Gateway는 초기부터 도입해 클라이언트 단의 엔드포인트 변경을 최소화합니다.
11. 운영 체크리스트
스키마 컴포지션에 실패하는 충돌이 없는지 사전 검증합니다. Subgraph 버저닝과 롤백 전략을 명확히 합니다. 에러·성능 메트릭을 Gateway와 Subgraph 모두에서 수집합니다. Supergraph SDL을 CI에서 생성·서명·배포해 변경을 추적 가능하게 관리합니다.
12. 문제 해결 가이드
필드가 누락되면 @key 정의가 일치하는지, __resolveReference가 올바르게 구현되었는지 점검합니다. N+1 문제가 보이면 서브그래프에서 데이터로더를 적용합니다. 캐시 미스가 잦다면 keyFields·__typename 일관성과 Fragment 사용을 재검토합니다.
위 과정을 따르면 React 앱에서 Federation의 장점을 그대로 누리면서 팀 자율성과 성능을 동시에 확보할 수 있습니다. 먼저 작은 서브그래프 두세 개로 시작해 운영 툴링과 배포 파이프라인을 안정화한 뒤 점진적으로 확장하는 것을 권장합니다.
'React' 카테고리의 다른 글
| React 앱에서 CSS Grid를 활용한 반응형 레이아웃 구현 (0) | 2026.06.25 |
|---|---|
| React에서 컴포넌트 렌더링 우선순위 제어하기 (0) | 2026.06.25 |
| React에서 REST API 에러 처리 및 재시도 로직 구현하기 (0) | 2026.06.24 |
| React 앱에서 서버 로그 데이터를 실시간 모니터링하기 (0) | 2026.06.23 |
| React에서 복수 언어 번역 관리 자동화 툴 구축하기 (0) | 2026.06.23 |