6.1 KiB
6.1 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| backend-api-gateway-bff | API Gateway / BFF — 라우팅 / aggregation | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
API Gateway / BFF
Gateway = 횡단 관심사 (auth / rate / logging) 묶기. BFF = 프론트별 (web / iOS / android) 응답 최적화. 마이크로서비스 + 다양한 클라.
📖 핵심 개념
- API Gateway: 단일 진입점 — Kong / Envoy / Cloudflare / API Gateway (AWS).
- BFF: Backend for Frontend — 클라별 다른 backend.
- Aggregation: 여러 service 호출 합쳐서 응답.
- Transformation: legacy ↔ modern format.
💻 코드 패턴
Gateway 책임
Client ──▶ Gateway ──▶ Service A
└──▶ Service B
└──▶ Service C
Gateway:
- TLS termination
- Auth (JWT 검증)
- Rate limiting (per IP / per user)
- Logging / tracing
- CORS
- Routing / load balance
- Caching (GET)
Kong (DB-less)
# kong.yml
_format_version: "3.0"
services:
- name: users
url: http://users-svc:3000
routes:
- name: users-route
paths: [/api/users]
plugins:
- name: jwt
- name: rate-limiting
config: { minute: 100, policy: local }
Cloudflare Workers (가벼운 gateway)
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const url = new URL(req.url);
// Auth
const token = req.headers.get('authorization')?.replace('Bearer ', '');
const userId = await verifyJwt(token, env.JWT_SECRET);
if (!userId && !isPublic(url.pathname)) return new Response('unauthorized', { status: 401 });
// Rate limit
const ok = await env.RATE.limit({ key: userId ?? req.headers.get('cf-connecting-ip')! });
if (!ok.success) return new Response('rate limited', { status: 429 });
// Routing
if (url.pathname.startsWith('/api/users')) {
return fetch(`http://users-svc${url.pathname}`, { headers: { 'x-user-id': userId } });
}
if (url.pathname.startsWith('/api/orders')) {
return fetch(`http://orders-svc${url.pathname}`, { headers: { 'x-user-id': userId } });
}
return new Response('not found', { status: 404 });
},
};
BFF (Web-specific)
// bff-web/routes/dashboard.ts
app.get('/api/dashboard', async (req, res) => {
// 동시에 여러 service 호출
const [user, orders, recommendations] = await Promise.all([
fetch(`${USERS}/users/${req.userId}`).then(r => r.json()),
fetch(`${ORDERS}/orders?userId=${req.userId}&limit=10`).then(r => r.json()),
fetch(`${RECS}/for/${req.userId}`).then(r => r.json()),
]);
// 프론트가 필요한 형태로 합치기
res.json({
user: { id: user.id, name: user.name, avatar: user.avatar },
recentOrders: orders.map(o => ({ id: o.id, status: o.status, total: o.total })),
recommendations: recommendations.slice(0, 5).map(r => ({ id: r.id, name: r.name })),
});
});
→ Web 만 사용. iOS BFF 가 다른 페이로드 반환.
Pattern: Backend for X
bff-web/ → 큰 페이로드, 많은 데이터
bff-mobile/ → 작은 페이로드, 압축
bff-admin/ → 추가 관리 endpoint
Aggregation + Cache
const cache = new Map<string, { data: unknown; expires: number }>();
async function withCache<T>(key: string, ttl: number, fn: () => Promise<T>): Promise<T> {
const c = cache.get(key);
if (c && c.expires > Date.now()) return c.data as T;
const data = await fn();
cache.set(key, { data, expires: Date.now() + ttl });
return data;
}
const recs = await withCache(`recs:${userId}`, 60_000, () => fetch(...));
Header propagation (tracing)
const traceHeaders = ['traceparent', 'tracestate', 'x-request-id', 'x-user-id'];
async function forward(req: Request, target: string) {
const h = new Headers();
for (const k of traceHeaders) {
const v = req.headers.get(k);
if (v) h.set(k, v);
}
return fetch(target, { headers: h });
}
Apollo Federation (GraphQL gateway)
// 여러 GraphQL service 를 하나의 schema 로
import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://users:4001' },
{ name: 'orders', url: 'http://orders:4002' },
],
}),
});
const server = new ApolloServer({ gateway });
tRPC (TypeSafe RPC)
// router (server)
const appRouter = router({
user: userRouter,
order: orderRouter,
});
export type AppRouter = typeof appRouter;
// client
import type { AppRouter } from 'server/router';
const trpc = createTRPCClient<AppRouter>({ url: '/api/trpc' });
const u = await trpc.user.get.query({ id: '1' }); // typed end-to-end
→ BFF 와 자연 결합.
🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| 단일 service | Gateway 불필요 |
| 마이크로서비스 + 단일 클라 | Gateway 만 |
| 다양한 클라 (web/iOS/AOS) | Gateway + BFF per-client |
| Edge / 글로벌 | Cloudflare Workers / Vercel Edge |
| GraphQL 통합 | Apollo Federation |
| TypeScript 풀스택 | tRPC (BFF 비슷) |
❌ 안티패턴
- Gateway 가 비즈니스 로직: gateway = thin. 로직은 service.
- BFF 가 모든 클라 공유: 의미 없음. per-client 또는 그냥 service.
- Aggregation 안 cache: 매번 다중 호출. 5분 cache 만이라도.
- Header propagation 누락: tracing 깨짐.
- Auth 가 service 마다: gateway 한 번 + service 는 신뢰.
- Service 간 Sync 호출 chain: latency 합산. 병렬 / async.
- Gateway = single point of failure 무 HA: 다중 노드 + LB.
🤖 LLM 활용 힌트
- Gateway = 횡단 (auth/rate/log) + routing + LB.
- BFF = 클라별 응답 형태.
- Tracing 헤더 항상 propagate.