Files
2nd/10_Wiki/Topics/Coding/Backend_API_Gateway_BFF.md
T
2026-05-09 21:08:02 +09:00

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
backend
api-gateway
bff
vibe-coding
language applicable_to
TS / Kong / Envoy
Backend
API gateway
BFF
backend for frontend
Kong
Envoy
Apollo Federation

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.

🔗 관련 문서