Skip to content

필사 모드: APIゲートウェイパターンとBFF設計:Kong・Envoy・GraphQL Federationの実践実装

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

はじめに

マイクロサービスアーキテクチャが産業全般に普及する中、サービス間通信の複雑さをどのように管理するかは、すべてのエンジニアリング組織の中核的な課題となっています。グローバルAPI管理市場は2026年時点で約51億ドル規模に成長し、CAGR 32.3%の爆発的な拡大を見せています。2025年時点で組織の31%が複数のAPIゲートウェイを運用しており、そのうち11%は3つ以上のゲートウェイを並行運用しています。

APIゲートウェイパターンは、クライアントとバックエンドサービスの間に単一のエントリーポイント(Single Entry Point)を配置し、ルーティング、認証、レートリミッティング、プロトコル変換などの横断的関心事(Cross-Cutting Concerns)を集約するアーキテクチャパターンです。ここにBFF(Backend for Frontend)パターンを組み合わせることで、Web、モバイル、IoTなど各フロントエンドタイプに最適化された専用バックエンドを提供できます。

本記事では、APIゲートウェイパターンとBFFパターンのコア原理を検討し、Kong Gateway、Envoy Proxy、GraphQL Federationという3つの主要ツールの実践的な構成方法を扱います。認証・認可の統合、レートリミッティング、サーキットブレーカー、オブザーバビリティ(Observability)の設定まで、本番運用に必要なすべての内容を実践コードとともに提供します。

APIゲートウェイパターンの理解

ゲートウェイの役割

APIゲートウェイは、マイクロサービスアーキテクチャにおけるすべてのクライアントリクエストの単一エントリーポイントの役割を果たします。内部サービストポロジーの複雑さを抽象化し、クライアントにクリーンで一貫したインターフェースを提供します。主な責務は以下の通りです。

- **リクエストルーティング**: URLパス、ヘッダー、メソッドに基づいて適切なバックエンドサービスにトラフィックを転送

- **プロトコル変換**: REST-to-gRPC、HTTP-to-WebSocketなどプロトコル間の変換

- **レスポンス集約**: 複数のサービスのレスポンスを1つにまとめてクライアントに返却(API Composition)

- **横断的関心事の処理**: 認証、認可、レートリミッティング、ロギング、キャッシング、CORSなどを一元管理

- **サービスディスカバリ**: 動的にサービスインスタンスを探索しロードバランシングを実行

ゲートウェイトポロジーアーキテクチャ

API 게이트웨이 토폴로지

┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐

│ Web App │ │Mobile App│ │ Partner │ │IoT Device│

│ (React) │ │ (Swift) │ │ (B2B) │ │ (MQTT) │

└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘

│ │ │ │

└──────┬──────┴──────┬──────┘ │

│ │ │

┌──────▼──────┐ ┌───▼──────────┐ ┌──────▼──────┐

│ Edge LB │ │ Edge LB │ │ Edge LB │

│ (L4/L7) │ │ (L4/L7) │ │ (L4/L7) │

└──────┬──────┘ └──────┬───────┘ └──────┬──────┘

│ │ │

┌──────▼───────────────▼──────────────────▼──────┐

│ API Gateway Cluster │

│ ┌──────────────────────────────────────────┐ │

│ │ Auth │ Rate Limit │ Logging │ Transform │ │

│ └──────────────────────────────────────────┘ │

│ ┌─────────────────────────────────────────┐ │

│ │ Route Matching & Dispatch │ │

│ └─────────────────────────────────────────┘ │

└───────┬──────────┬──────────┬──────────┬───────┘

│ │ │ │

┌──────▼───┐┌────▼────┐┌────▼────┐┌───▼──────┐

│ User Svc ││Order Svc││Pay Svc ││Notify Svc│

│ (gRPC) ││ (REST) ││ (REST) ││(WebSocket│

└──────────┘└─────────┘└─────────┘└──────────┘

Edgeゲートウェイ vs Internalゲートウェイ

実務では、ゲートウェイを階層的に配置します。Edgeゲートウェイは外部トラフィックのエントリーポイントとしてDDoS防御、TLS終端、積極的なレートリミッティングを実行します。Internalゲートウェイはサービス間通信を管理し、サービスディスカバリ、mTLS、きめ細かなアクセス制御を担当します。

외부 트래픽

┌───▼────────────────────────┐

│ Edge Gateway (Kong) │ ← TLS 종료, WAF, 레이트 리미팅

│ - DDoS 방어 │

│ - JWT 검증 │

│ - 글로벌 레이트 리미팅 │

└───────────┬────────────────┘

┌───────────▼────────────────┐

│ Internal Gateway (Envoy) │ ← mTLS, 서비스 라우팅

│ - mTLS 상호 인증 │

│ - 서비스 디스커버리 │

│ - 세밀한 RBAC │

└──┬────────┬────────┬───────┘

│ │ │

┌──▼──┐ ┌──▼──┐ ┌──▼──┐

│Svc A│ │Svc B│ │Svc C│

└─────┘ └─────┘ └─────┘

BFF(Backend for Frontend)パターン

BFFパターンが必要な理由

単一のAPIゲートウェイですべてのクライアントにサービスを提供していると、必然的に「一つのサイズがすべてに合わない」問題に直面します。Webアプリケーションは豊富なデータを一度に取得したいのに対し、モバイルアプリは帯域幅節約のため最小限のフィールドのみ必要です。IoTデバイスはバイナリプロトコルを、サードパーティパートナーは安定したREST APIを期待します。

BFFパターンはSam Newmanが提案したアーキテクチャパターンで、各フロントエンドタイプに合った専用バックエンドを配置してこの問題を解決します。BFFは特定のユーザー体験に密接に結合され、そのUIを担当するチームがBFFも合わせて管理します。

BFFアーキテクチャ

┌──────────┐ ┌──────────┐ ┌──────────┐

│ Web App │ │Mobile App│ │ Admin App│

│ (React) │ │ (Flutter)│ │ (Vue.js) │

└────┬─────┘ └────┬─────┘ └────┬─────┘

│ │ │

┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐

│ Web BFF │ │Mobile BFF│ │Admin BFF │

│(Node.js) │ │ (Go) │ │(Node.js) │

│ │ │ │ │ │

│- 풍부한 │ │- 경량 │ │- 대시보드│

│ 데이터 │ │ 페이로드│ │ 집계 │

│- SSR 지원│ │- 오프라인│ │- 벌크 │

│- SEO │ │ 캐싱 │ │ 연산 │

└──┬───┬───┘ └──┬───┬───┘ └──┬───┬───┘

│ │ │ │ │ │

│ └────────┬───┘ └────────┬───┘ │

│ │ │ │

┌──▼──┐ ┌───▼──┐ ┌───▼──┐ │

│User │ │Order │ │Admin │◄──┘

│ Svc │ │ Svc │ │ Svc │

└─────┘ └──────┘ └──────┘

BFF実装例:Node.js Web BFF

// web-bff/src/server.ts

const app = express()

// Web BFF: 대시보드를 위한 데이터 집계 엔드포인트

app.get('/api/dashboard', async (req, res) => {

const userId = req.headers['x-user-id'] as string

try {

// 여러 마이크로서비스에서 병렬로 데이터 수집

const [userProfile, recentOrders, notifications, analytics] = await Promise.all([

axios.get(`${process.env.USER_SERVICE_URL}/users/${userId}`),

axios.get(`${process.env.ORDER_SERVICE_URL}/orders?userId=${userId}&limit=10`),

axios.get(`${process.env.NOTIFICATION_SERVICE_URL}/notifications/${userId}?unread=true`),

axios.get(`${process.env.ANALYTICS_SERVICE_URL}/users/${userId}/summary`),

])

// Web 프론트엔드에 최적화된 응답 구조

res.json({

user: {

name: userProfile.data.name,

email: userProfile.data.email,

avatar: userProfile.data.avatarUrl,

memberSince: userProfile.data.createdAt,

},

orders: {

recent: recentOrders.data.items.map((order: any) => ({

id: order.id,

status: order.status,

total: order.totalAmount,

date: order.createdAt,

itemCount: order.items.length,

// 웹에서만 필요한 상세 정보 포함

trackingUrl: order.trackingUrl,

invoice: order.invoiceUrl,

})),

totalCount: recentOrders.data.totalCount,

},

notifications: {

unreadCount: notifications.data.count,

items: notifications.data.items.slice(0, 5),

},

analytics: {

totalSpent: analytics.data.totalSpent,

orderFrequency: analytics.data.orderFrequency,

favoriteCategories: analytics.data.topCategories,

},

})

} catch (error) {

// 부분 실패 시 가용한 데이터만 반환 (Graceful Degradation)

res.status(207).json({

partial: true,

error: 'Some services are unavailable',

available: {},

})

}

})

// Mobile BFF와의 차이점: 웹은 SSR을 위한 전체 데이터를 반환

// Mobile BFF는 동일 엔드포인트에서 경량 필드만 반환

app.listen(3001, () => {

console.log('Web BFF running on port 3001')

})

BFFコア設計原則

1. **1つのBFFは1つのフロントエンドに対応**: Web BFFがモバイルの要件を処理し始めると「多目的BFF」となり、元の問題に逆戻りします。

2. **BFFはビジネスロジックを持たない**: データ集約、変換、フォーマッティングのみ実行し、ビジネスルールは必ず下位サービスに配置します。

3. **BFFチームとフロントエンドチームは同一チーム**: フロントエンド開発者が自分のBFFを直接管理する時が最も効率的です。

4. **セキュリティ境界としてのBFF**: 特にSPA環境でOIDC/OAuth 2.0フローのトークンネゴシエーションをBFFがConfidential Clientとして処理し、パブリッククライアントのセキュリティリスクを排除します。

ゲートウェイツール比較

主要なAPIゲートウェイソリューションを本番運用の観点から比較します。

| 項目 | Kong Gateway | Envoy Proxy | AWS API Gateway | GraphQL Federation |

| ------------------------ | ------------------------------ | ------------------------------- | --------------------- | ---------------------------- |

| **基盤技術** | NGINX + Lua | C++ (自社開発) | AWS 관리형 | Apollo Router (Rust) |

| **デプロイモデル** | セルフホスティング / Konnect | セルフホスティング / サイドカー | フルマネージド | セルフホスティング / GraphOS |

| **プロトコル** | HTTP, gRPC, WebSocket | HTTP/1.1, HTTP/2, gRPC, TCP | REST, WebSocket, HTTP | GraphQL |

| **性能** | ~50,000 TPS/노드 | ~100,000+ TPS/노드 | AWS 내부 최적화 | サブグラフ数に比例 |

| **拡張性** | 플러그인 (Lua, Go) | 필터 (C++, Wasm, Lua) | Lambda 연동 | 서브그래프 서비스 |

| **K8s統合** | KIC (Gateway API 준수) | Envoy Gateway / Istio | EKS 통합 | Helm Chart |

| **オブザーバビリティ** | Prometheus, Datadog | OpenTelemetry 네이티브 | CloudWatch | Apollo Studio |

| **認証** | 플러그인 (JWT, OAuth) | 필터 (JWT, ext_authz) | Cognito, Lambda Auth | 서브그래프 위임 |

| **レートリミッティング** | 플러그인 (ローカル/グローバル) | 필터 (ローカル/グローバル) | スロットリング内蔵 | カスタム実装が必要 |

| **ライセンス** | Apache 2.0 / Enterprise | Apache 2.0 | 従量制 | Elastic License |

| **学習曲線** | 中程度 | 高い | 低い | 高い |

| **適合シナリオ** | 汎用API管理 | サービスメッシュ / L7制御 | AWS 네이티브 | マルチチームグラフ統合 |

選択ガイド

- **Kong**: プラグインエコシステムが豊富でGUI管理ツールが必要な場合。60以上の公式プラグインを提供しています。ただし、OIDC、高度な分析などのコア機能がEnterpriseライセンスに含まれる点に注意が必要です。

- **Envoy**: 高性能L7プロキシが必要な場合やサービスメッシュ(Istio、Consul)との統合が必要な場合。xDS APIによる動的設定が強みですが、設定の複雑さが高いです。

- **AWS API Gateway**: AWSネイティブ環境で管理負担を最小化したい場合。Lambda統合が強力ですが、AWSロックインとコールドスタート遅延が欠点です。

- **GraphQL Federation**: 多数のチームが独立してグラフを提供し、フロントエンドが柔軟なクエリを必要とする場合。スキーマ設計の初期投資は大きいですが、長期的にフロントエンドの生産性が最大化されます。

Kong Gateway本番構成

Kongのインストールと基本構成(Kubernetes)

Kong Ingress Controller(KIC)はKubernetes Gateway APIをファーストクラスシチズンとしてサポートし、Gateway APIコア適合性テストを100%パスした最初のゲートウェイです。

kong-values.yaml - Helm Chart 설정

helm install kong kong/ingress -f kong-values.yaml -n kong-system

gateway:

image:

repository: kong/kong-gateway

tag: '3.9'

env:

database: 'off' # DB-less 모드 (선언적 설정)

proxy_access_log: /dev/stdout

admin_access_log: /dev/stdout

proxy_error_log: /dev/stderr

admin_error_log: /dev/stderr

plugins: bundled,oidc,prometheus

proxy:

type: LoadBalancer

annotations:

service.beta.kubernetes.io/aws-load-balancer-type: nlb

service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing

tls:

enabled: true

admin:

enabled: true

type: ClusterIP # 클러스터 내부에서만 접근

resources:

requests:

cpu: 500m

memory: 512Mi

limits:

cpu: 2000m

memory: 2Gi

controller:

image:

repository: kong/kubernetes-ingress-controller

tag: '3.4'

ingressClass: kong

resources:

requests:

cpu: 250m

memory: 256Mi

Kongサービスとルーティング構成

kong-gateway-api.yaml

Kubernetes Gateway API를 사용한 선언적 라우팅 구성

apiVersion: gateway.networking.k8s.io/v1

kind: Gateway

metadata:

name: kong-gateway

namespace: kong-system

annotations:

konghq.com/gateway-operator: 'true'

spec:

gatewayClassName: kong

listeners:

- name: http

protocol: HTTP

port: 80

- name: https

protocol: HTTPS

port: 443

tls:

mode: Terminate

certificateRefs:

- name: api-tls-cert

kind: Secret

HTTPRoute로 서비스별 라우팅 정의

apiVersion: gateway.networking.k8s.io/v1

kind: HTTPRoute

metadata:

name: user-service-route

namespace: default

annotations:

konghq.com/plugins: rate-limiting-global,jwt-auth,cors

spec:

parentRefs:

- name: kong-gateway

namespace: kong-system

hostnames:

- 'api.example.com'

rules:

- matches:

- path:

type: PathPrefix

value: /api/v1/users

backendRefs:

- name: user-service

port: 8080

weight: 100

- matches:

- path:

type: PathPrefix

value: /api/v1/orders

backendRefs:

- name: order-service

port: 8080

weight: 90

- name: order-service-canary

port: 8080

weight: 10 # 카나리 배포: 10% 트래픽

Kong 플러그인: JWT 인증

apiVersion: configuration.konghq.com/v1

kind: KongPlugin

metadata:

name: jwt-auth

namespace: default

config:

key_claim_name: iss

claims_to_verify:

- exp

header_names:

- Authorization

plugin: jwt

Kong 플러그인: 레이트 리미팅

apiVersion: configuration.konghq.com/v1

kind: KongPlugin

metadata:

name: rate-limiting-global

namespace: default

config:

minute: 100

hour: 5000

policy: redis

redis:

host: redis-master.redis-system.svc.cluster.local

port: 6379

database: 0

timeout: 2000

fault_tolerant: true # Redis 장애 시에도 요청 통과

hide_client_headers: false

plugin: rate-limiting

Kong 플러그인: CORS

apiVersion: configuration.konghq.com/v1

kind: KongPlugin

metadata:

name: cors

namespace: default

config:

origins:

- 'https://app.example.com'

- 'https://admin.example.com'

methods:

- GET

- POST

- PUT

- DELETE

- OPTIONS

headers:

- Authorization

- Content-Type

- X-Request-ID

exposed_headers:

- X-RateLimit-Remaining

max_age: 3600

credentials: true

plugin: cors

Envoy Proxy構成

Envoy基本構成

Envoyは高性能なC++ベースのプロキシで、L7層でHTTP/2、gRPCをネイティブにサポートします。xDS APIによる動的設定更新がコアの強みです。

envoy-config.yaml

Envoy 프록시 정적 설정 (프로덕션에서는 xDS 동적 설정 사용 권장)

admin:

address:

socket_address:

address: 0.0.0.0

port_value: 9901

static_resources:

listeners:

- name: api_listener

address:

socket_address:

address: 0.0.0.0

port_value: 8443

filter_chains:

- filters:

- name: envoy.filters.network.http_connection_manager

typed_config:

'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager

stat_prefix: ingress_http

codec_type: AUTO

액세스 로그 설정

access_log:

- name: envoy.access_loggers.file

typed_config:

'@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog

path: /dev/stdout

log_format:

json_format:

timestamp: '%START_TIME%'

method: '%REQ(:METHOD)%'

path: '%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%'

protocol: '%PROTOCOL%'

response_code: '%RESPONSE_CODE%'

duration_ms: '%DURATION%'

upstream_host: '%UPSTREAM_HOST%'

request_id: '%REQ(X-REQUEST-ID)%'

HTTP 필터 체인

http_filters:

JWT 인증 필터

- name: envoy.filters.http.jwt_authn

typed_config:

'@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication

providers:

auth0_provider:

issuer: 'https://your-tenant.auth0.com/'

audiences:

- 'https://api.example.com'

remote_jwks:

http_uri:

uri: 'https://your-tenant.auth0.com/.well-known/jwks.json'

cluster: auth0_jwks

timeout: 5s

cache_duration: 600s

forward: true

payload_in_metadata: jwt_payload

rules:

- match:

prefix: /api/

requires:

provider_name: auth0_provider

- match:

prefix: /health

레이트 리미팅 필터

- name: envoy.filters.http.local_ratelimit

typed_config:

'@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit

stat_prefix: http_local_rate_limiter

token_bucket:

max_tokens: 1000

tokens_per_fill: 100

fill_interval: 1s

filter_enabled:

runtime_key: local_rate_limit_enabled

default_value:

numerator: 100

denominator: HUNDRED

filter_enforced:

runtime_key: local_rate_limit_enforced

default_value:

numerator: 100

denominator: HUNDRED

response_headers_to_add:

- append_action: OVERWRITE_IF_EXISTS_OR_ADD

header:

key: x-local-rate-limit

value: 'true'

라우터 필터 (반드시 마지막)

- name: envoy.filters.http.router

typed_config:

'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

라우트 설정

route_config:

name: local_route

virtual_hosts:

- name: api_service

domains: ['api.example.com']

routes:

- match:

prefix: '/api/v1/users'

route:

cluster: user_service

timeout: 10s

retry_policy:

retry_on: '5xx,connect-failure,reset'

num_retries: 3

per_try_timeout: 3s

retry_back_off:

base_interval: 0.1s

max_interval: 1s

- match:

prefix: '/api/v1/orders'

route:

cluster: order_service

timeout: 15s

retry_policy:

retry_on: '5xx,connect-failure'

num_retries: 2

transport_socket:

name: envoy.transport_sockets.tls

typed_config:

'@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext

common_tls_context:

tls_certificates:

- certificate_chain:

filename: /etc/envoy/certs/server.crt

private_key:

filename: /etc/envoy/certs/server.key

clusters:

- name: user_service

type: STRICT_DNS

lb_policy: ROUND_ROBIN

load_assignment:

cluster_name: user_service

endpoints:

- lb_endpoints:

- endpoint:

address:

socket_address:

address: user-service.default.svc.cluster.local

port_value: 8080

서킷 브레이커 설정

circuit_breakers:

thresholds:

- priority: DEFAULT

max_connections: 1024

max_pending_requests: 1024

max_requests: 1024

max_retries: 3

헬스 체크

health_checks:

- timeout: 5s

interval: 10s

unhealthy_threshold: 3

healthy_threshold: 2

http_health_check:

path: /health

Outlier Detection (이상 탐지)

outlier_detection:

consecutive_5xx: 5

interval: 10s

base_ejection_time: 30s

max_ejection_percent: 50

- name: order_service

type: STRICT_DNS

lb_policy: LEAST_REQUEST

load_assignment:

cluster_name: order_service

endpoints:

- lb_endpoints:

- endpoint:

address:

socket_address:

address: order-service.default.svc.cluster.local

port_value: 8080

circuit_breakers:

thresholds:

- priority: DEFAULT

max_connections: 512

max_pending_requests: 512

- name: auth0_jwks

type: LOGICAL_DNS

lb_policy: ROUND_ROBIN

load_assignment:

cluster_name: auth0_jwks

endpoints:

- lb_endpoints:

- endpoint:

address:

socket_address:

address: your-tenant.auth0.com

port_value: 443

transport_socket:

name: envoy.transport_sockets.tls

typed_config:

'@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext

sni: your-tenant.auth0.com

GraphQL Federation

Apollo Federation v2の概要

GraphQL Federationは、複数のチームが独立して管理するサブグラフ(Subgraph)を1つの統合されたスーパーグラフ(Supergraph)に合成するアーキテクチャパターンです。Apollo Federation v2は改善された共有オーナーシップモデル、向上したタイプマージ、よりクリーンな構文を提供します。

┌──────────────────────────────────────────────────┐

│ 클라이언트 │

│ (Web / Mobile App) │

└────────────────────┬─────────────────────────────┘

│ GraphQL 쿼리

┌────────────────────▼─────────────────────────────┐

│ Apollo Router │

│ (슈퍼그래프 실행 엔진) │

│ │

│ ┌────────────────────────────────────────────┐ │

│ │ Query Plan (쿼리 실행 계획) │ │

│ │ 1. users 서브그래프에서 User 조회 │ │

│ │ 2. orders 서브그래프에서 Order 조회 │ │

│ │ 3. reviews 서브그래프에서 Review 조회 │ │

│ │ 4. 결과 병합 후 클라이언트에 반환 │ │

│ └────────────────────────────────────────────┘ │

└───────┬───────────────┬───────────────┬──────────┘

│ │ │

┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐

│ Users │ │ Orders │ │ Reviews │

│ Subgraph │ │ Subgraph │ │ Subgraph │

│ (Team A) │ │ (Team B) │ │ (Team C) │

│ │ │ │ │ │

│ - User │ │ - Order │ │ - Review │

│ - Profile │ │ - LineItem │ │ - Rating │

│ │ │ - extends │ │ - extends │

│ │ │ User │ │ User │

└──────────────┘ └─────────────┘ └─────────────┘

Federationサブグラフの実装

// subgraphs/users/src/index.ts

const typeDefs = gql`

extend schema

@link(

url: "https://specs.apollo.dev/federation/v2.11"

import: ["@key", "@shareable", "@external", "@provides"]

)

type Query {

user(id: ID!): User

users(limit: Int = 10, offset: Int = 0): UserConnection!

me: User

}

type User @key(fields: "id") {

id: ID!

email: String!

name: String!

avatarUrl: String

role: UserRole!

createdAt: String!

updatedAt: String!

}

type UserConnection {

nodes: [User!]!

totalCount: Int!

pageInfo: PageInfo!

}

type PageInfo @shareable {

hasNextPage: Boolean!

hasPreviousPage: Boolean!

}

enum UserRole {

ADMIN

USER

MODERATOR

}

`

const resolvers = {

Query: {

user: async (_: any, { id }: { id: string }, context: any) => {

return context.dataSources.usersAPI.getUser(id)

},

users: async (_: any, args: any, context: any) => {

return context.dataSources.usersAPI.getUsers(args)

},

me: async (_: any, __: any, context: any) => {

if (!context.userId) throw new Error('Not authenticated')

return context.dataSources.usersAPI.getUser(context.userId)

},

},

User: {

__resolveReference: async (ref: { id: string }, context: any) => {

// Federation이 다른 서브그래프에서 User를 참조할 때 호출

return context.dataSources.usersAPI.getUser(ref.id)

},

},

}

const server = new ApolloServer({

schema: buildSubgraphSchema({ typeDefs, resolvers }),

})

const { url } = await startStandaloneServer(server, {

listen: { port: 4001 },

context: async ({ req }) => ({

userId: req.headers['x-user-id'],

dataSources: {

usersAPI: new UsersDataSource(),

},

}),

})

console.log(`Users subgraph ready at ${url}`)

// subgraphs/orders/src/index.ts

const typeDefs = gql`

extend schema

@link(

url: "https://specs.apollo.dev/federation/v2.11"

import: ["@key", "@external", "@requires"]

)

type Query {

order(id: ID!): Order

ordersByUser(userId: ID!, status: OrderStatus): [Order!]!

}

User 타입을 확장하여 orders 필드 추가

type User @key(fields: "id") {

id: ID! @external

orders(status: OrderStatus, limit: Int = 10): [Order!]!

totalSpent: Float! @requires(fields: "id")

}

type Order @key(fields: "id") {

id: ID!

userId: ID!

user: User!

items: [OrderItem!]!

status: OrderStatus!

totalAmount: Float!

currency: String!

createdAt: String!

shippingAddress: Address

}

type OrderItem {

productId: ID!

productName: String!

quantity: Int!

unitPrice: Float!

}

type Address {

street: String!

city: String!

country: String!

zipCode: String!

}

enum OrderStatus {

PENDING

CONFIRMED

SHIPPED

DELIVERED

CANCELLED

}

`

const resolvers = {

Query: {

order: async (_: any, { id }: { id: string }, ctx: any) => {

return ctx.dataSources.ordersAPI.getOrder(id)

},

ordersByUser: async (_: any, args: any, ctx: any) => {

return ctx.dataSources.ordersAPI.getOrdersByUser(args.userId, args.status)

},

},

User: {

orders: async (user: { id: string }, args: any, ctx: any) => {

return ctx.dataSources.ordersAPI.getOrdersByUser(user.id, args.status)

},

totalSpent: async (user: { id: string }, _: any, ctx: any) => {

return ctx.dataSources.ordersAPI.getTotalSpent(user.id)

},

},

Order: {

user: (order: { userId: string }) => ({ __typename: 'User', id: order.userId }),

},

}

スーパーグラフ構成

supergraph-config.yaml

rover supergraph compose --config supergraph-config.yaml > supergraph.graphql

federation_version: =2.11.2

subgraphs:

users:

routing_url: http://users-subgraph:4001/graphql

schema:

subgraph_url: http://users-subgraph:4001/graphql

orders:

routing_url: http://orders-subgraph:4002/graphql

schema:

subgraph_url: http://orders-subgraph:4002/graphql

reviews:

routing_url: http://reviews-subgraph:4003/graphql

schema:

subgraph_url: http://reviews-subgraph:4003/graphql

router-config.yaml (Apollo Router 설정)

supergraph:

listen: 0.0.0.0:4000

introspection: false # 프로덕션에서는 비활성화

헤더 전파

headers:

all:

request:

- propagate:

named: 'Authorization'

- propagate:

named: 'X-Request-ID'

- propagate:

named: 'X-User-ID'

서브그래프별 설정

traffic_shaping:

all:

timeout: 10s

subgraphs:

orders:

timeout: 15s # 주문 서비스는 더 긴 타임아웃

쿼리 깊이 제한 (DoS 방지)

limits:

max_depth: 15

max_height: 200

max_aliases: 30

텔레메트리

telemetry:

exporters:

tracing:

otlp:

enabled: true

endpoint: http://otel-collector:4317

protocol: grpc

metrics:

prometheus:

enabled: true

listen: 0.0.0.0:9090

path: /metrics

응답 캐싱

preview_entity_cache:

enabled: true

subgraph:

all:

enabled: true

ttl: 60s

認証と認可の統合

ゲートウェイ層JWT検証ミドルウェア

ゲートウェイでJWTを検証し、検証済みのクレームをヘッダーに変換して下位サービスに転送するのが標準パターンです。RS256またはES256アルゴリズムで署名されたトークンを使用し、IdPの公開鍵で署名を検証します。

// gateway/src/middleware/auth.ts

// JWKS 클라이언트 (키 캐싱 포함)

const client = jwksClient({

jwksUri: `${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,

cache: true,

cacheMaxEntries: 5,

cacheMaxAge: 600000, // 10분

rateLimit: true,

jwksRequestsPerMinute: 10,

})

function getSigningKey(header: jwt.JwtHeader): Promise<string> {

return new Promise((resolve, reject) => {

client.getSigningKey(header.kid, (err, key) => {

if (err) return reject(err)

const signingKey = key?.getPublicKey()

if (!signingKey) return reject(new Error('No signing key found'))

resolve(signingKey)

})

})

}

// 경로별 인증 정책

const AUTH_POLICIES: Record<string, { required: boolean; scopes?: string[] }> = {

'/api/v1/users/me': { required: true, scopes: ['read:profile'] },

'/api/v1/orders': { required: true, scopes: ['read:orders'] },

'/api/v1/admin': { required: true, scopes: ['admin:all'] },

'/health': { required: false },

'/api/v1/products': { required: false }, // 공개 API

}

export async function authMiddleware(

req: Request,

res: Response,

next: NextFunction

): Promise<void> {

// 경로별 정책 조회

const policy = Object.entries(AUTH_POLICIES).find(([path]) => req.path.startsWith(path))

if (policy && !policy[1].required) {

return next()

}

const authHeader = req.headers.authorization

if (!authHeader?.startsWith('Bearer ')) {

res.status(401).json({

error: 'unauthorized',

message: 'Missing or invalid Authorization header',

})

return

}

const token = authHeader.substring(7)

try {

// JWT 디코딩 (검증 전 헤더 확인)

const decoded = jwt.decode(token, { complete: true })

if (!decoded || typeof decoded === 'string') {

throw new Error('Invalid token format')

}

// 알고리즘 화이트리스트 검증

if (!['RS256', 'ES256'].includes(decoded.header.alg)) {

throw new Error(`Unsupported algorithm: ${decoded.header.alg}`)

}

// JWKS에서 공개 키를 가져와 서명 검증

const signingKey = await getSigningKey(decoded.header)

const payload = jwt.verify(token, signingKey, {

algorithms: ['RS256', 'ES256'],

audience: process.env.API_AUDIENCE,

issuer: `${process.env.AUTH0_DOMAIN}/`,

clockTolerance: 30, // 30초 시계 오차 허용

}) as jwt.JwtPayload

// 스코프 검증

if (policy?.[1]?.scopes) {

const tokenScopes = (payload.scope || '').split(' ')

const requiredScopes = policy[1].scopes

const hasAllScopes = requiredScopes.every((s) => tokenScopes.includes(s))

if (!hasAllScopes) {

res.status(403).json({

error: 'insufficient_scope',

message: `Required scopes: ${requiredScopes.join(', ')}`,

})

return

}

}

// 검증된 클레임을 헤더로 전파 (하위 서비스에서 사용)

req.headers['x-user-id'] = payload.sub || ''

req.headers['x-user-email'] = payload.email || ''

req.headers['x-user-roles'] = JSON.stringify(payload['https://api.example.com/roles'] || [])

req.headers['x-auth-time'] = String(payload.iat || 0)

// 원본 Authorization 헤더는 제거 (하위 서비스에 토큰 노출 방지)

delete req.headers.authorization

next()

} catch (error: any) {

const errorMap: Record<string, { status: number; message: string }> = {

TokenExpiredError: { status: 401, message: 'Token has expired' },

JsonWebTokenError: { status: 401, message: 'Invalid token' },

NotBeforeError: { status: 401, message: 'Token not yet valid' },

}

const mapped = errorMap[error.name] || { status: 401, message: 'Authentication failed' }

res.status(mapped.status).json({ error: error.name, message: mapped.message })

}

}

レートリミッティングとサーキットブレーカー

多段階レートリミッティング戦略

効果的なレートリミッティングは単一層ではなく多段階で構成されます。EdgeではIP基準の積極的な制限でDDoSを防御し、ゲートウェイではユーザー/APIキーベースのきめ細かな制限を適用します。

// gateway/src/middleware/rateLimiter.ts

const redis = new Redis({

host: process.env.REDIS_HOST || 'localhost',

port: parseInt(process.env.REDIS_PORT || '6379'),

maxRetriesPerRequest: 3,

retryStrategy: (times) => Math.min(times * 100, 3000),

enableReadyCheck: true,

})

interface RateLimitConfig {

windowMs: number // 시간 창 (밀리초)

maxRequests: number // 최대 요청 수

keyPrefix: string // Redis 키 프리픽스

}

// 슬라이딩 윈도우 레이트 리미터 (Redis Sorted Set 활용)

async function slidingWindowRateLimit(

identifier: string,

config: RateLimitConfig

): Promise<{ allowed: boolean; remaining: number; retryAfter?: number }> {

const key = `${config.keyPrefix}:${identifier}`

const now = Date.now()

const windowStart = now - config.windowMs

// Redis 트랜잭션으로 원자적 처리

const pipeline = redis.pipeline()

pipeline.zremrangebyscore(key, 0, windowStart) // 만료된 항목 제거

pipeline.zadd(key, now.toString(), `${now}:${Math.random()}`) // 현재 요청 추가

pipeline.zcard(key) // 현재 윈도우 내 요청 수

pipeline.expire(key, Math.ceil(config.windowMs / 1000)) // TTL 설정

const results = await pipeline.exec()

if (!results) throw new Error('Redis pipeline failed')

const currentCount = results[2]?.[1] as number

const allowed = currentCount <= config.maxRequests

const remaining = Math.max(0, config.maxRequests - currentCount)

if (!allowed) {

// 가장 오래된 요청이 만료되는 시점 계산

const oldestInWindow = await redis.zrange(key, 0, 0, 'WITHSCORES')

const retryAfter =

oldestInWindow.length >= 2

? Math.ceil((parseInt(oldestInWindow[1]) + config.windowMs - now) / 1000)

: Math.ceil(config.windowMs / 1000)

return { allowed: false, remaining: 0, retryAfter }

}

return { allowed: true, remaining }

}

// 티어별 레이트 리미팅 정책

const RATE_LIMIT_TIERS: Record<string, RateLimitConfig> = {

free: { windowMs: 60_000, maxRequests: 60, keyPrefix: 'rl:free' },

pro: { windowMs: 60_000, maxRequests: 600, keyPrefix: 'rl:pro' },

enterprise: { windowMs: 60_000, maxRequests: 6000, keyPrefix: 'rl:enterprise' },

// IP 기반 글로벌 제한 (DDoS 방어)

global_ip: { windowMs: 1_000, maxRequests: 50, keyPrefix: 'rl:ip' },

}

export async function rateLimitMiddleware(req: any, res: any, next: any) {

const clientIp = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.ip

const userId = req.headers['x-user-id']

const tier = req.headers['x-user-tier'] || 'free'

try {

// 1단계: IP 기반 글로벌 레이트 리미팅

const ipResult = await slidingWindowRateLimit(clientIp, RATE_LIMIT_TIERS.global_ip)

if (!ipResult.allowed) {

res.set('Retry-After', String(ipResult.retryAfter))

return res.status(429).json({ error: 'Too many requests (IP limit)' })

}

// 2단계: 사용자/티어 기반 레이트 리미팅

if (userId) {

const config = RATE_LIMIT_TIERS[tier] || RATE_LIMIT_TIERS.free

const userResult = await slidingWindowRateLimit(userId, config)

res.set('X-RateLimit-Limit', String(config.maxRequests))

res.set('X-RateLimit-Remaining', String(userResult.remaining))

if (!userResult.allowed) {

res.set('Retry-After', String(userResult.retryAfter))

return res.status(429).json({ error: 'Rate limit exceeded', tier })

}

}

next()

} catch (error) {

// Redis 장애 시 요청을 통과시킴 (fault-tolerant)

console.error('Rate limiter error:', error)

next()

}

}

サーキットブレーカーパターン

サーキットブレーカーは失敗率が閾値を超えると回路を開いて高速な失敗レスポンスを返し、クールダウン後にHalf-Open状態で段階的に復旧を試みます。

// gateway/src/middleware/circuitBreaker.ts

enum CircuitState {

CLOSED = 'CLOSED', // 정상 - 요청 통과

OPEN = 'OPEN', // 차단 - 즉시 실패 반환

HALF_OPEN = 'HALF_OPEN', // 테스트 - 제한적 요청 허용

}

interface CircuitBreakerConfig {

failureThreshold: number // 실패 임계치 (예: 5)

successThreshold: number // 복구 판정 성공 횟수 (예: 3)

timeout: number // Open 상태 유지 시간 (ms)

monitorWindow: number // 모니터링 윈도우 (ms)

halfOpenMaxRequests: number // Half-Open에서 허용할 최대 요청

}

class CircuitBreaker {

private state: CircuitState = CircuitState.CLOSED

private failureCount = 0

private successCount = 0

private lastFailureTime = 0

private halfOpenRequests = 0

constructor(

private name: string,

private config: CircuitBreakerConfig

) {}

async execute<T>(fn: () => Promise<T>, fallback?: () => T): Promise<T> {

if (this.state === CircuitState.OPEN) {

if (Date.now() - this.lastFailureTime >= this.config.timeout) {

this.transitionTo(CircuitState.HALF_OPEN)

} else {

console.warn(`[CircuitBreaker:${this.name}] OPEN - rejecting request`)

if (fallback) return fallback()

throw new Error(`Service ${this.name} is unavailable (circuit open)`)

}

}

if (this.state === CircuitState.HALF_OPEN) {

if (this.halfOpenRequests >= this.config.halfOpenMaxRequests) {

if (fallback) return fallback()

throw new Error(`Service ${this.name} is testing recovery`)

}

this.halfOpenRequests++

}

try {

const result = await fn()

this.onSuccess()

return result

} catch (error) {

this.onFailure()

if (fallback && this.state === CircuitState.OPEN) return fallback()

throw error

}

}

private onSuccess() {

if (this.state === CircuitState.HALF_OPEN) {

this.successCount++

if (this.successCount >= this.config.successThreshold) {

this.transitionTo(CircuitState.CLOSED)

}

}

this.failureCount = 0

}

private onFailure() {

this.failureCount++

this.lastFailureTime = Date.now()

if (this.failureCount >= this.config.failureThreshold) {

this.transitionTo(CircuitState.OPEN)

}

}

private transitionTo(newState: CircuitState) {

console.log(`[CircuitBreaker:${this.name}] ${this.state} -> ${newState}`)

this.state = newState

if (newState === CircuitState.CLOSED) {

this.failureCount = 0

this.successCount = 0

}

if (newState === CircuitState.HALF_OPEN) {

this.halfOpenRequests = 0

this.successCount = 0

}

}

getStatus() {

return {

name: this.name,

state: this.state,

failureCount: this.failureCount,

lastFailureTime: this.lastFailureTime ? new Date(this.lastFailureTime).toISOString() : null,

}

}

}

// 서비스별 서킷 브레이커 인스턴스

export const circuitBreakers = {

userService: new CircuitBreaker('user-service', {

failureThreshold: 5,

successThreshold: 3,

timeout: 30_000, // 30초 후 Half-Open 전환

monitorWindow: 60_000, // 1분 모니터링 윈도우

halfOpenMaxRequests: 3,

}),

orderService: new CircuitBreaker('order-service', {

failureThreshold: 3,

successThreshold: 2,

timeout: 60_000, // 결제 관련은 보수적으로 60초

monitorWindow: 60_000,

halfOpenMaxRequests: 1,

}),

}

オブザーバビリティの統合

ゲートウェイはすべてのトラフィックが通過するポイントであるため、オブザーバビリティの中核ハブとなります。ログ、メトリクス、トレースの3つの柱(Three Pillars)をすべてゲートウェイ層で収集する必要があります。

OpenTelemetry統合設定

otel-collector-config.yaml

OpenTelemetry Collector 설정 - 게이트웨이 트래픽 수집

receivers:

otlp:

protocols:

grpc:

endpoint: 0.0.0.0:4317

http:

endpoint: 0.0.0.0:4318

processors:

batch:

timeout: 5s

send_batch_size: 1024

게이트웨이 메타데이터 추가

attributes:

actions:

- key: deployment.environment

value: production

action: upsert

- key: service.layer

value: gateway

action: upsert

샘플링 (전체의 10%만 상세 트레이스)

tail_sampling:

decision_wait: 10s

policies:

- name: error-policy

type: status_code

status_code:

status_codes: [ERROR]

- name: latency-policy

type: latency

latency:

threshold_ms: 1000

- name: probabilistic-policy

type: probabilistic

probabilistic:

sampling_percentage: 10

exporters:

otlphttp/jaeger:

endpoint: http://jaeger:4318

prometheus:

endpoint: 0.0.0.0:8889

loki:

endpoint: http://loki:3100/loki/api/v1/push

labels:

resource:

service.name: 'service_name'

service.layer: 'layer'

service:

pipelines:

traces:

receivers: [otlp]

processors: [batch, attributes, tail_sampling]

exporters: [otlphttp/jaeger]

metrics:

receivers: [otlp]

processors: [batch, attributes]

exporters: [prometheus]

logs:

receivers: [otlp]

processors: [batch, attributes]

exporters: [loki]

コアメトリクスダッシュボード項目

ゲートウェイで必ずモニタリングすべきメトリクスは以下の通りです。

- **リクエストレート(Request Rate)**: 毎秒リクエスト数。異常な急増はDDoSまたはクライアントバグを意味します。

- **エラーレート(Error Rate)**: 4xx、5xxレスポンスの割合。サービスごとに分離して追跡します。

- **レイテンシ(Latency)**: p50、p95、p99パーセンタイル。p99がSLAを超過するとアラート発生。

- **サーキットブレーカー状態**: 各サービスのサーキット状態をリアルタイムで表示します。

- **レートリミッティングヒット率**: 制限に引っかかるリクエストの割合。正常なユーザーが頻繁に制限される場合、閾値の調整が必要です。

- **アップストリーム接続プール**: アクティブ接続、待機リクエスト、タイムアウト数を追跡します。

トラブルシューティング

障害シナリオ1:ゲートウェイメモリリーク

**症状**: ゲートウェイPodのメモリ使用量が持続的に増加してOOM Killが発生。

**原因**: 大容量のリクエスト/レスポンスボディをバッファリングしながらメモリを解放できない場合。特にファイルアップロードAPIや大容量JSONレスポンスをプロキシする際に発生。

**解決策**:

Kong: 요청/응답 본문 버퍼링 제한

apiVersion: configuration.konghq.com/v1

kind: KongPlugin

metadata:

name: request-size-limiting

config:

allowed_payload_size: 10 # MB

size_unit: megabytes

require_content_length: true

plugin: request-size-limiting

Envoy: 버퍼 제한 설정

http_filters:

- name: envoy.filters.http.buffer

typed_config:

'@type': type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer

max_request_bytes: 10485760 # 10MB

障害シナリオ2:カスケード障害

**症状**: 1つのバックエンドサービスの障害がゲートウェイを通じてシステム全体に伝播。

**原因**: サーキットブレーカー未設定またはタイムアウトが長すぎる場合。障害サービスへのリクエストがゲートウェイの接続プールを枯渇させる。

**復旧手順**:

1. 障害サービスに対するサーキットブレーカーを直ちに手動で開放

2. 該当サービスルートに静的フォールバックレスポンスを設定

3. 接続プールの状態を確認し、必要に応じてゲートウェイPodのローリング再起動

4. 障害サービス復旧後、トラフィックを段階的に復元(カナリア)

障害シナリオ3:TLS証明書の期限切れ

**症状**: 突然のAPI全体503エラー。クライアントでSSL/TLSハンドシェイク失敗ログを確認。

**予防**: cert-managerを活用した自動証明書更新と、期限切れ30日前のアラート設定。

인증서 만료일 확인 스크립트

#!/bin/bash

DOMAIN="api.example.com"

EXPIRY=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null \

| openssl x509 -noout -dates 2>/dev/null \

| grep notAfter | cut -d= -f2)

EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$EXPIRY" +%s)

NOW_EPOCH=$(date +%s)

DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

echo "Certificate expires: $EXPIRY ($DAYS_LEFT days remaining)"

if [ "$DAYS_LEFT" -lt 30 ]; then

echo "WARNING: Certificate expiring soon!"

슬랙/PagerDuty 알림 전송

fi

障害シナリオ4:GraphQL Federationサブグラフスキーマの衝突

**症状**: `rover supergraph compose`が失敗。2つのサブグラフが同じタイプの同じフィールドを異なる戻り値型で定義。

**解決策**: Federation v2の`@shareable`ディレクティブを使用して共有タイプフィールドを明示的に宣言し、`@override`ディレクティブでフィールドのオーナーシップを移行します。CI/CDパイプラインで`rover subgraph check`を実行して互換性を事前検証する必要があります。

本番チェックリスト

デプロイ前の検証

- ゲートウェイクラスタを最低3ノード以上でHA構成

- TLS 1.3の強制適用と弱い暗号スイートの無効化

- ヘルスチェックエンドポイント(`/health`、`/ready`)の構成

- リクエスト/レスポンスサイズ制限の設定(デフォルト10MB以下)

- タイムアウト設定:接続5秒、読み取り30秒、書き込み30秒

- DNS TTLの適切な設定(長すぎると障害復旧が遅延)

セキュリティ

- すべての外部通信にTLS適用、内部通信にmTLS適用

- JWT検証はゲートウェイで実行、トークンは下位サービスに転送しない

- CORS許可ドメインのホワイトリスト適用

- レートリミッティング:IPベース(DDoS)、ユーザーベース(公平使用)の二重適用

- 機密ヘッダー(Authorization、Cookie)のロギングでのマスキング

- Admin APIを内部ネットワークからのみアクセス可能に制限

オブザーバビリティ

- 分散トレーシング:すべてのリクエストにX-Request-IDを付与して伝播

- メトリクス:RED(Rate、Errors、Duration)メトリクスの収集とダッシュボード構築

- アラート:エラー率5%超過、p99レイテンシSLA超過、サーキットブレーカーOpen時に即時アラート

- ロギング:構造化JSONログ、リクエスト/レスポンスボディはデバッグモードでのみロギング

運用

- カナリアデプロイ:ゲートウェイ設定変更時に5-10%のトラフィックで検証後、全体適用

- 設定バージョン管理:すべてのゲートウェイ設定をGitで管理(GitOps)

- ロールバック手順:30秒以内に以前の設定にロールバック可能な手順を確保

- 負荷テスト:予想ピークトラフィックの2倍以上で定期的に負荷テストを実施

- カオスエンジニアリング:ゲートウェイノード1台の障害時の自動復旧を検証

アンチパターンと失敗事例

アンチパターン1:God Gateway(神ゲートウェイ)

すべてのビジネスロジックをゲートウェイに実装するパターン。リクエスト検証、データ変換、ビジネスルール適用、レスポンス集約をすべてゲートウェイが処理すると、ゲートウェイがモノリス化します。ゲートウェイはルーティング、認証、レートリミッティングなどの横断的関心事のみ処理すべきです。

アンチパターン2:共有BFF

1つのBFFがWebとモバイルの両方にサービスを提供し始めると、双方の要件が衝突して複雑さが指数関数的に増加します。「このフィールドはモバイルでのみ必要」という条件文がBFF全体に広がり、BFFがなかった時よりも悪い状況になります。各フロントエンドタイプごとに専用のBFFを維持してください。

アンチパターン3:ゲートウェイでのデータ変換の濫用

ゲートウェイで過度なレスポンス変換(フィールド名変更、データ構造の再配置、ビジネスロジックに基づくフィールドフィルタリング)を実行すると、ゲートウェイのCPU使用量が急増しデバッグが困難になります。データ変換はBFFまたはサービス自体で実行すべきです。

アンチパターン4:レートリミッティングなしのデプロイ

「我々のサービスはトラフィックが少ないのでレートリミッティングは不要」というのは最もよくある間違いです。予期しないクローラー、不正に実装されたクライアントの無限リトライ、APIキー漏洩による不正使用はトラフィック規模に関係なく発生します。本番デプロイ時のレートリミッティングは選択ではなく必須です。

アンチパターン5:サーキットブレーカー未適用の同期呼び出しチェーン

ゲートウェイがサービスAを呼び出し、サービスAがサービスBを呼び出す同期チェーンでサーキットブレーカーがない場合、サービスBの障害がサービスAとゲートウェイを連鎖的にダウンさせます(カスケード障害)。すべての外部呼び出しポイントにサーキットブレーカーを適用し、可能であれば非同期通信に切り替えてください。

失敗事例:GraphQL Federation N+1クエリ問題

Federation環境でRouterがサブグラフAからユーザーリストを取得した後、各ユーザーの注文をサブグラフBから個別に照会するとN+1問題が発生します。Apollo RouterのQuery Planは可能な限りバッチリクエストで最適化しますが、サブグラフがバッチ照会をサポートしない場合、性能が急激に低下します。サブグラフの`__resolveReference`がバッチをサポートするようDataLoaderパターンを必ず適用する必要があります。

参考資料

- [Microservices Pattern: API Gateway / Backends for Frontends](https://microservices.io/patterns/apigateway.html) - Chris Richardsonのマイクロサービスパターンリファレンス

- [Sam Newman - Backends For Frontends](https://samnewman.io/patterns/architectural/bff/) - BFFパターンの原著者による説明

- [Kong Gateway Kubernetes Documentation](https://docs.konghq.com/gateway/latest/install/kubernetes/) - Kong公式Kubernetesインストールガイド

- [Envoy Proxy Documentation](https://www.envoyproxy.io/) - Envoyプロキシ公式ドキュメント

- [Introduction to Apollo Federation](https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/federation) - Apollo GraphQL Federation公式ドキュメント

- [The Backend for Frontend Pattern (BFF) - Auth0](https://auth0.com/blog/the-backend-for-frontend-pattern-bff/) - Auth0のBFFパターンとセキュリティガイド

- [Backends for Frontends Pattern - Azure Architecture Center](https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends) - Microsoft AzureのBFFパターンリファレンス

- [Kubernetes Gateway API Implementations](https://gateway-api.sigs.k8s.io/implementations/) - Kubernetes Gateway API実装一覧

- [API Gateway Patterns for Microservices - Kong vs NGINX vs Envoy](https://medium.com/@hydrurdgn/api-gateway-patterns-for-microservices-comparing-kong-nginx-and-envoy-eb899f5bbebd) - ゲートウェイ比較分析

현재 단락 (1/1137)

マイクロサービスアーキテクチャが産業全般に普及する中、サービス間通信の複雑さをどのように管理するかは、すべてのエンジニアリング組織の中核的な課題となっています。グローバルAPI管理市場は2026年時点...

작성 글자: 0원문 글자: 32,965작성 단락: 0/1137