BFF와 API Gateway, 3조건은 같고 축이 다르다
Gateway Pattern — 경계를 어디에 긋는가
난이도: ★★★☆☆
연관 노트: ‘보험/뱅크/증권’은 마이크로서비스가 아니다
핵심 요약
- 경계가 다른데 1개로 묶어서 깨진다. 3조건 중 깨지는 건 경계 조건이다.
- 단일 진입점은 동일하고, 검문/변환도 문제가 아니다. JWT·PII·외부 스키마 변환을 각각 하는 건 괜찮다 — 게이트웨이 조건이라고 해서 검문/변환을 꼭 1개만 하는 건 아니니까.
- 그러나 경계가 다르다. 외부 파트너사는 파트너사 서버 ↔ 내부 서비스 경계고, JWT·rate limit은 브라우저 ↔ 내부 백엔드 경계다.
rate limit과JWT 검증은 축이 아니라 검문 행위다. 3조건의 “검문+변환”에 들어가는 것이고, “경계를 나누는 축”과는 다른 층이다.- 축을 정하는 건 “무엇을 호출하는가”가 아니라 “왜 반드시 여기를 지나게 만들었는가”다. ← 이 노트의 핵심
- 같은 외부 벤더 호출이라도, 화면에 필요한 데이터를 조합하려고 거치면 화면 축(BFF), 규정 준수 때문에 반드시 거쳐야 하면 정책 축(Gateway)이다.
왜 헷갈렸나
오해 1: “API Gateway는 처음 듣는 개념”
대화 초반에 이미 예시로 나왔던 개념이었다(외부 클라이언트 ↔ 내부 마이크로서비스들). “게이트웨이 패턴의 한 사례”로 스쳐 지나갔을 뿐, BFF와 비교해서 뜯어본 적이 없어서 새 개념처럼 느껴졌다.
오해 2: 축과 검문 행위를 같은 층으로 놓음
“rate limit/JWT은 정책 축이고, 외부 벤더 요청은 정책이 아닌 축”이라고 나눴다. 그런데 rate limit과 JWT는 경계에서 수행하는 행위이고, 화면/정책은 경계 자체를 나누는 기준이다. 층이 다른 두 개를 같은 리스트에 올려놓으니 분류가 안 됐다.
오해 3: “외부 요청이면 화면 축이 아니다”
“외부 요청”이라는 사실 자체는 축을 결정하지 않는다. 호출 대상이 아니라 통과를 강제한 이유가 축을 결정한다.
주의할 반대 방향의 오해: “검문/변환이 여러 개면 게이트웨이가 아니다”
이건 오해다. 게이트웨이는 검문·변환을 몇 개 하느냐로 판정하지 않는다. JWT 검증 + PII 필터 + 벤더별 스키마 변환을 한 곳에서 전부 해도 게이트웨이 조건은 깨지지 않는다. 3조건의 “검문+변환”은 개수 제한이 아니라 행위의 존재 여부를 묻는다.
깨지는 건 개수가 아니라 경계다. 검문이 10개라도 경계가 하나면 건강하고, 검문이 2개라도 경계가 둘이면 문제다.
같은 판정이 데이터 쪽에도 그대로 적용된다 — 운영(OLTP) 경계와 분석(OLAP) 경계도 축이 다르므로 한 곳에서 처리하면 안 된다.
메커니즘
게이트웨이 3조건
어떤 컴포넌트가 게이트웨이인지 판정하는 기준.
⚠️ “3조건”이라는 말이 두 곳에서 쓰인다. 여기의 3조건은 “서비스 앞단에 무엇을 세울지”를 판정하고, 마이크로서비스 3조건(독립 배포 / 독립 데이터 / 단일 책임)은 “서비스를 어디서 쪼갤지”를 판정한다. 별개다.
1. 경계(boundary) : 서로 다른 신뢰 영역 사이에 있는가
2. 단일 진입점 : 그 경계를 넘는 모든 트래픽이 반드시 여기를 지나는가
3. 검문 + 변환 : 통과 여부를 검증하고, 형태를 바꾸는가
BFF도, API Gateway도 3조건을 모두 만족한다. 그래서 3조건만으로는 둘을 구분할 수 없다.
구분 기준: 3조건을 “어느 단위”로 적용했는가
| 조건 | BFF | API Gateway |
|---|---|---|
| 경계 | 특정 화면(앱 하나) ↔ 여러 백엔드 | 모든 외부 소비자 ↔ 전체 서비스군 |
| 단일 진입점 | ”그 화면” 전용 진입점 — 앱마다 여러 개 존재 가능 (웹BFF, 모바일BFF) | 조직 전체 트래픽이 통과하는 진짜 하나 |
| 검문+변환 | 변환 목적 = UI가 필요한 모양으로 조합(aggregation) | 검문 목적 = 인증/rate limit/라우팅 등 인프라 정책 적용 |
| 변경 이유 | UI 요구사항 — “이 화면에 이 데이터 조합이 필요해짐” | 인프라·보안 정책 — “인증 방식 교체”, “계약 등급 신설” |
| 소유 팀 | 프론트엔드 팀 | 플랫폼·인프라 팀 |
한 줄로: BFF는 3조건을 “화면 단위”로 적용한 것, API Gateway는 “전사 인프라 정책 단위”로 적용한 것.
축을 판정하는 질문
같은 코드를 보고도 축이 갈린다. 판정 기준은 하나다.
"왜 반드시 여기를 지나게 만들었는가?"
│
┌──────────────┴──────────────┐
▼ ▼
"이 화면에 필요한 데이터를 "규정/보안 때문에 모든
조합해야 하니까" 트래픽이 거쳐야 하니까"
│ │
▼ ▼
화면 축 = BFF 정책 축 = API Gateway
(UI 바뀌면 변경) (정책 바뀌면 변경)
“외부 서드파티 벤더를 호출한다”는 사실은 이 판정에 아무 정보도 주지 않는다. 호출 대상은 축을 결정하지 않는다.
안티패턴: 두 경계를 한 파일에 묶기
한 API Route에 rate limit + JWT 검증을 넣고 웹·모바일·파트너사 요청을 전부 받는 구조.
경계 A — 브라우저 ↔ 내부 백엔드 (화면 축)
[웹앱] ─┐
[모바일앱] ─┤
├──▶ [ API Route 하나 ] ◀── 두 경계가 뒤섞임
[파트너사] ─┘
경계 B — 파트너사 서버 ↔ 내부 서비스 (정책 축)
※ 파트너사 요청은 브라우저에서 오지 않는다.
출발지가 다르므로 신뢰 영역도 다르다.
3조건 중 깨지는 것: 경계. 단일 진입점은 오히려 더 잘 뭉쳐지고, 검문+변환도 형식상 성립한다. 문제는 경계가 2개인데 1개로 묶었다는 것.
터지는 지점:
| 증상 | 원인 |
|---|---|
| 화면 문구 하나 고치려고 배포했는데 파트너사 트래픽이 같이 내려감 | 서로 다른 변경 주기가 한 배포 단위에 묶임 |
| 프론트팀 PR에 플랫폼팀 인증 정책 diff가 섞임 | 소유 팀이 충돌 |
| 이 라우트 하나 죽으면 전 채널 정지 | 장애 범위가 두 경계의 합집합이 됨 (SPOF) |
분리 방향
[웹앱] ──▶ [ 웹 BFF ] ──┐
[모바일앱] ──▶ [ 모바일 BFF ] ──┼──▶ [ 내부 서비스들 ]
│
[파트너사] ──▶ [ API Gateway ] ─┘
인증/rate limit
BFF는 소비자마다 하나씩(N개 허용), Gateway는 정책 경계에 하나. 각자 자기 축의 변경만 흡수한다.
다음에 이 상황을 만나면
API Route에 새 책임을 추가하려 할 때 → 축을 먼저 판정한다.
- 이 라우트를 통과하도록 강제한 이유가 뭔가? (호출 대상이 아니라 이유)
- 그 이유가 기존 책임의 이유와 같은 축인가? 다르면 파일을 나눈다
- 추가하려는 게 축인가 검문 행위인가? rate limit/JWT/스키마 검증은 행위 — 축을 새로 만들지 않는다
- 이 라우트가 죽으면 몇 개 채널이 멈추나? 두 개 이상이면 경계를 합쳐놓은 것
- 이 파일을 고칠 때 다른 팀 리뷰가 필요한가? 필요하면 소유가 갈려 있다는 신호
Next.js 특유의 함정: app/api/*는 서버 전용 번들이라 벤더 API 키가 클라이언트로 노출되지 않는다 — 이건 Build 레이어의 보장이다. “게이트웨이는 Network 문제”라고 단정하면 이 층을 놓친다.
커넥팅 닷
← 선행 개념 (이걸 알아야 이해된다)
- ‘보험/뱅크/증권’은 마이크로서비스가 아니다 — API Gateway가 “여러 서비스 앞단”에 서는 이유는 그 뒤에 독립 배포되는 서비스들이 있기 때문. 마이크로서비스 없이는 Gateway의 존재 이유가 절반 사라진다
- 왜 서버는 이전 요청을 기억하지 않는가 — 게이트웨이가 요청을 자유롭게 라우팅·재시도할 수 있는 건 각 요청이 독립적이기 때문
- 신뢰 경계(trust boundary) — 3조건 중 1번. “신뢰할 수 없는 입력이 신뢰 영역으로 들어오는 지점”이 어디인지 못 그리면 게이트웨이를 어디 둘지 정할 수 없다
→ 확장 개념 (여기서 더 나아가면)
- Sidecar / Service Mesh (Envoy, Istio) — 게이트웨이의 검문 로직을 각 서비스 옆 프록시로 밀어낸 형태. “정책을 앞단 하나에 모을까, 모든 서비스 옆에 붙일까”의 트레이드오프
- Edge Middleware — 게이트웨이를 CDN 엣지까지 끌어올린 것. 오리진 도달 전에 검문하면 지연이 줄지만 실행 환경이 제약된다
- API Composition vs CQRS Read Model — BFF의 aggregation을 요청 시점에 할지, 미리 조합된 읽기 모델을 만들어둘지
↔ 같은 원리가 적용되는 곳
- ‘쓰기도 하고 읽기도 하는데’ 죽은 코드다 — 경계를 선언하지 않으면 책임이 스며든다는 같은 문제. 저긴 데이터 소유, 여긴 트래픽 소유
- 섹션 사이 구분선, 컴포넌트 대신 플래그로 — “무엇이 함께 바뀌는가”로 단위를 정하는 동일한 판단. 축이 다르면 분리한다
- Single Responsibility Principle의 정확한 정의 — “하나의 일만 한다”가 아니라 **“변경 이유가 하나”**다. 화면 축과 정책 축이 한 파일에 있으면 변경 이유가 둘이라 SRP 위반
부록: “단일 진입점”의 함정
3조건의 “단일 진입점”을 물리적으로 파일 하나로 오해하기 쉽다. 실제 의미는 “그 경계를 넘는 트래픽이 우회로 없이 여기를 지난다”이다.
그래서 BFF는 여러 개 있어도 각각이 단일 진입점이다. 웹BFF와 모바일BFF는 서로 다른 경계를 담당하므로, 각자 자기 경계의 유일한 통로다.
반대로 위 안티패턴은 파일이 하나인데 단일 진입점이 아니다 — 두 경계의 통로를 겸하고 있어서, 어느 경계 기준으로도 “그 경계만의 진입점”이 아니다. 파일 개수와 진입점 개수는 무관하다.
참고
- Pattern: Backends For Frontends — Sam Newman — BFF 패턴의 원 출처. “소비자당 하나”라는 원칙이 왜 필요한지
- Microservices Patterns: API Gateway — microservices.io — API Gateway와 BFF variant를 같은 문서에서 대조