API 오케스트레이션은 하나의 조정 계층이 여러 API를 순서대로 호출하고 하나로 결합된 결과를 반환하는 패턴입니다. 지휘자 비유가 어디서나 쓰이는 이유는 그것이 정확하기 때문입니다. 각 API는 자기 파트를 연주하지만, 총보(순서, 의존성, 금관 파트가 실패하면 어떻게 되는지)는 오케스트레이터에 있습니다. 바로 짚어야 할 혼동이 하나 있습니다. 업계의 절반은 “오케스트레이션”을 자사 제품 카테고리가 하는 일이라는 뜻으로 씁니다(게이트웨이, 자동화 플랫폼, 워크플로 엔진, GraphQL 라우터가 모두 이 단어를 내세웁니다). 이 항목은 패턴 그 자체를 뜻하며, 아래에서 제품들을 구분합니다.
핵심 요약
| 질문 | 답변 |
|---|---|
| 형태 | 요청 하나 입력 → 순서 있고 조건적인 호출 → 응답 하나 반환 |
| vs. 집계 | 집계는 무상태 병렬 읽기를 팬아웃, 오케스트레이션은 상태를 가진 순차 실행 |
| vs. 코레오그래피 | 중앙의 명령 vs. 이벤트에 대한 분산된 반응 |
| 실패의 진실 | 서비스 간 롤백은 존재하지 않음, 답은 보상(사가) |
| 지연 시간의 지렛대 | 의존성 그래프가 막지 않는 모든 것을 병렬화 |
오케스트레이션된 체크아웃
// JavaScript — Cloud Code (cloud/main.js)
// Lightweight orchestration: one function owns the checkout flow
Parse.Cloud.define('checkout', async (req) => {
const { cartId } = req.params;
// Independent lookups run in PARALLEL (~200 ms, not 400)
const [cart, address] = await Promise.all([
loadCart(cartId),
loadAddress(req.user),
]);
const reservation = await reserveInventory(cart); // step 1
try {
const charge = await chargeCard(req.user, cart, {
idempotencyKey: cartId, // safe to retry
});
const shipment = await createShipment(charge, address); // step 3
return { orderId: shipment.orderId }; // one response out
} catch (e) {
await releaseInventory(reservation); // compensate — no rollback exists
throw e;
}
}); // Flutter / Dart — Back4app Flutter SDK
// The client sees ONE call — the orchestrator owns the sequence
final result = await ParseCloudFunction('checkout')
.execute(parameters: {'cartId': cartId});
showConfirmation(result.result['orderId']);
// Without orchestration this screen would call payment, inventory,
// and shipping itself — three round trips, and the error handling too. // iOS / Swift — Back4app Swift SDK
// The client sees ONE call — the orchestrator owns the sequence
let result: [String: String] = try await Cloud.run(
name: "checkout", parameters: ["cartId": cartId])
showConfirmation(result["orderId"] ?? "")
// Without orchestration this screen would call payment, inventory,
// and shipping itself — three round trips, and the error handling too. // Android / Kotlin — Back4app Android SDK
// The client sees ONE call — the orchestrator owns the sequence
val params = mapOf("cartId" to cartId)
val result = ParseCloud.callFunction<Map<String, Any>>("checkout", params)
showConfirmation(result["orderId"] as String)
// Without orchestration this screen would call payment, inventory,
// and shipping itself — three round trips, and the error handling too. 이 패턴의 모든 것이 함수 하나에 들어 있습니다. 독립적인 조회는 병렬화하고, 의존적인 단계는 순서대로 실행하며, 멱등성 키로 위험한 단계를 재시도 가능하게 만들고, catch 블록에 보상을 둡니다. 카드가 거절되었다고 재고 예약이 스스로 풀리지는 않기 때문입니다.
게이트웨이 vs. BFF vs. 집계 vs. 오케스트레이션 vs. 코레오그래피
어떤 경쟁 글도 제공하지 않는 다섯 가지 개념 비교표입니다.
| 정의 | 단계 간 상태 | 로직이 있는 곳 | 예시 | |
|---|---|---|---|---|
| API 게이트웨이 | 횡단 관심사를 위한 리버스 프록시 | 없음 | 설정: 인증, 속도 제한, 라우팅 | 모든 API의 단일 진입점 |
| BFF | 클라이언트별 백엔드 | 요청 단위 | 하나의 프론트엔드에 맞춘 재구성 | 페이로드를 줄이는 모바일 BFF |
| 집계 / 컴포지션 | 병렬 팬아웃 + 병합 | 없음, 무상태 | 메모리 내 조인 | 4개 서비스를 읽는 대시보드 |
| 오케스트레이션 | 명령으로 진행되는 다단계 워크플로 | 있음, N단계가 N+1단계에 입력 | 오케스트레이터의 순서 + 오류 로직 | 체크아웃, 온보딩 |
| 코레오그래피 | 이벤트에 반응하는 서비스 | 분산되고 암묵적 | 각 소비자, 중앙의 두뇌 없음 | 팬아웃되는 주문 이벤트 |
마지막 두 행이 핵심 짝이며, EDA 항목에도 같은 규칙이 거울처럼 담겨 있습니다. 누군가 워크플로의 결과를 책임져야 한다면 오케스트레이션을, 생산자가 다음에 일어날 일에 정말로 관심이 없다면 코레오그래피를 선택합니다. 오케스트레이터는 명령(명령형, 거부 가능)으로 말하고, 코레오그래피는 이벤트(과거형 사실)로 말합니다. 같은 구분이 아키텍처 규모로 나타난 것입니다.
5단계 중 3단계가 실패할 때
검색 결과 상위 글들이 건너뛰는 부분이며, 오케스트레이션이 배관 작업이 아니라 엔지니어링인 이유입니다. 분산된 단계에는 공유 트랜잭션이 없습니다. 완료된 결제는 그 결제를 알지도 못했던 데이터베이스가 롤백할 수 없습니다. 사가 패턴이 정직한 답입니다. 각 단계는 보상 작업(결제 ↔ 환불, 예약 ↔ 해제)과 짝을 이룬 로컬 작업이며, 실패하면 이미 끝난 모든 단계의 보상을 실행합니다. 이를 작동시키는 세 가지 규율이 있습니다. 위험한 단계에는 멱등성 키를 두어 타임아웃 후 재시도가 이중 결제를 만들지 못하게 합니다(같은 키로 재시도된 결제는 중복 요청으로 인식되어 다시 실행되지 않습니다). 단계별 타임아웃을 두어 멈춘 의존성 하나가 흐름 전체를 멈추지 않게 합니다. 그리고 명시적인 종료 상태를 둡니다. 절반쯤 완료되고 보상된 흐름은 삼켜 버릴 예외가 아니라 기록해야 할 알려진 결과이기 때문입니다. 오케스트레이션의 장점은 이 모든 것이 눈에 보이는 한곳에 있다는 점이며, 그것은 곧 비용이기도 합니다. 그 한곳을 확장하고, 모니터링하고, 정확하게 관리해야 하기 때문입니다.
지연 시간 계산
각각 200ms인 호출 세 개를 순차 실행하면 600ms, 병렬 실행하면 약 200ms입니다. 오케스트레이터의 첫 번째 최적화는 캐싱이나 영리한 전송 방식이 아니라 의존성 그래프를 있는 그대로 읽는 것입니다. 장바구니와 주소 로드(서로 독립적)는 함께 실행하고, 결제(장바구니가 필요함)는 기다리며, 배송(결제가 필요함)은 결제를 기다립니다. 대부분의 오케스트레이션된 흐름은 짧은 순차 경로를 뼈대로 병렬 가지가 매달린 형태이며, 순차 경로에 잘못 놓인 단계 하나하나가 아무 이득 없이 사용자가 체감하는 지연 시간을 늘립니다.
코드인가, 엔진인가?
평범한 함수로 충분한 경우는 흐름이 짧고 동기적일 때입니다. 단계 몇 개, 전체 시간 예산 몇 초, 그리고 “실패했습니다”가 기다리는 클라이언트에 돌려줄 만한 답일 때입니다. 대부분의 앱 백엔드가 필요로 하는 오케스트레이션이 여기에 해당하며, 이것이 정확히 서버 측 함수가 제공하는 것입니다. 내구성 있는 워크플로 엔진(오픈소스 예: Temporal, Camunda)은 흐름이 오래 실행되거나(몇 분에서 며칠), 흐름 도중의 프로세스 재시작을 견뎌야 하거나, 예약된 재시도, 사람의 승인 단계, 재생 가능한 이력이 필요할 때 운영 부담만큼의 값을 합니다. 업그레이드 경로는 실재하지만 급한 경우는 드뭅니다. 안티패턴은 세 번 호출하는 체크아웃에 엔진을 배포하는 것, 또는 어느새 엔진이 되어 버린 함수 안에 내구성 있는 상태를 손수 구현하는 것입니다.
일반적인 사용 사례
- 체크아웃과 결제 흐름: 돈이 걸린 전형적인 의존적 순서입니다.
- 사용자 온보딩: 계정 생성, 신원 확인, 리소스 프로비저닝, 환영 메시지 발송. 순서가 있고 보상이 가능합니다.
- 모바일 화면 구성: 오케스트레이션된 호출 하나가 지연 시간이 큰 네트워크에서 잦은 왕복 세 번을 대체합니다.
- 서드파티 묶음: KYC 확인, 배송 견적, 데이터 보강. 여러 벤더, 하나의 답, 그리고 키는 서버 측에 보관합니다.
- 마이그레이션 이음매: 모놀리스 분해 동안 오케스트레이터가 기존 시스템과 새 시스템의 분할을 안정적인 API 하나 뒤에 숨깁니다.
오케스트레이션해야 할까? 결정 매트릭스
| 상황 | 선택할 것 |
|---|---|
| 하나의 사용자 동작 뒤에 의존적인 호출 3개 이상 | 오케스트레이션: 먼저 함수로 |
| 한 화면을 위한 독립적인 병렬 읽기 | 집계: 더 단순하고 무상태 |
| 횡단 관심사(인증, 제한) | 게이트웨이: 워크플로 로직이 아님 |
| 생산자가 신경 쓰지 않는 반응 | 이벤트 / 코레오그래피 |
| 사람이 개입하는 며칠짜리 흐름 | 내구성 있는 워크플로 엔진 |
| 백엔드 호출 하나 | 아무것도 필요 없음, 그냥 호출 |
한계와 트레이드오프
- 오케스트레이터는 의존성을 끌어당기는 자석입니다. 흐름 안의 모든 서비스를 알기 때문에 하위 서비스의 변경이 오케스트레이터로 파급됩니다. 가시성과 결합은 같은 속성의 양면입니다.
- 핫 패스 위에 있습니다. 모든 흐름이 오케스트레이터를 거치므로 그 지연 시간, 확장, 가용성 예산은 인프라의 각주가 아니라 제품의 일부입니다.
- 보상은 실행 취소가 아닙니다. 환불은 자체적인 실패 양상을 가진 새로운 이벤트이지 타임머신이 아닙니다. 사가는 원자성을 명시성과 맞바꾸며, 그 명시성은 직접 처리해야 합니다.
- 비즈니스 로직이 스며듭니다. 통제하지 않으면 오케스트레이터가 데이터를 소유한 서비스에 있어야 할 결정까지 흡수합니다. 흐름의 순서는 오케스트레이터에, 비즈니스 의미는 각 서비스에 두어야 합니다.
- 동기 오케스트레이션은 동기의 한계를 물려받습니다. 다섯 단계를 거치는 동안 클라이언트는 그저 기다립니다. 요청 시간 창을 넘어서는 흐름은 타임아웃을 늘릴 것이 아니라 진행 상태를 조회할 수 있는 작업으로 옮겨야 합니다.
Back4app에서의 오케스트레이션
Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. 가벼운 오케스트레이션은 Cloud Code 함수가 자연스럽게 하는 일이며, 코드 탭이 전체 패턴을 보여 줍니다. checkout 함수 하나가 독립적인 읽기를 병렬화하고, 의존적인 단계를 순서대로 실행하고, 서드파티 키를 서버 측에 보관하고, catch 블록에서 보상하며, 정확히 한 번 호출한 모바일 클라이언트에 응답 하나를 반환합니다. 함수는 데이터베이스 바로 옆에서 실행되고(주문 상태와 종료 결과는 쓰기 한 번이면 기록됩니다), 인증 컨텍스트는 request.user로 전달되며, 따로 배포하거나 확장할 오케스트레이션 계층이 없습니다. 언젠가 흐름이 요청 시간 창을 넘어서면(승인, 하루 동안의 대기) 그때가 워크플로 엔진으로 옮겨 갈 때입니다. 그전까지 오케스트레이터는 저장소 안의 파일 하나입니다.
자주 묻는 질문
API 오케스트레이션이란 무엇입니까?
여러 API 호출을 하나의 관리되는 워크플로로 조정하는 것입니다. 요청 하나가 들어오면 오케스트레이터가 의존성, 변환, 재시도, 오류를 처리하면서 각 백엔드 서비스를 올바른 순서와 올바른 데이터로 호출하고, 결합된 응답 하나를 내보냅니다. API들을 각 파트로 둔 오케스트라의 지휘자입니다.
오케스트레이션과 코레오그래피의 차이는 무엇입니까?
오케스트레이션에는 서비스에 명령하고 상태를 추적하는 중앙 코디네이터가 있습니다. 눈에 보이고 디버깅할 수 있지만 결합 지점이 됩니다. 코레오그래피에는 코디네이터가 없고 서비스들이 서로의 이벤트에 반응합니다. 결합은 최소화되지만 워크플로는 어디에도 명시적으로 존재하지 않습니다. 규칙은 누군가 결과를 책임져야 하면 오케스트레이션, 생산자가 다음에 일어날 일에 정말로 관심이 없으면 코레오그래피입니다.
API 게이트웨이는 오케스트레이터입니까?
아닙니다. 게이트웨이는 인증, 속도 제한, 라우팅 같은 횡단 관심사를 요청 단위로 처리하는 리버스 프록시입니다. 오케스트레이션은 단계 사이에 상태를 두고 여러 단계의 워크플로 로직을 관리합니다. 게이트웨이도 가벼운 응답 집계는 할 수 있지만, 2단계가 1단계의 출력에 의존하는 순간 게이트웨이의 영역을 벗어난 것입니다.
오케스트레이션과 집계의 차이는 무엇입니까?
상태입니다. 집계(API 컴포지션)는 서로 독립적인 호출을 보통 병렬로 팬아웃하고 응답을 병합하며, 어떤 호출도 다른 호출에 의존하지 않습니다. 오케스트레이션은 순차적이고 조건적입니다. N단계의 입력이 N−1단계의 출력에서 오고, 실패에는 보상이 필요하며, 흐름 자체가 로직입니다.
API 오케스트레이션의 예시에는 무엇이 있습니까?
전형적인 예는 체크아웃입니다. 장바구니 검증, 재고 예약, 카드 결제, 배송 생성, 확인 메일 발송까지 API 다섯 개가 엄격한 순서로 이어지며, 어느 단계에서 실패하든 앞서 한 일을 되돌려야 합니다. 여행 예약과 사용자 온보딩도 같은 형태입니다.
오케스트레이션된 흐름의 중간에서 실패하면 어떻게 처리합니까?
서비스 간 롤백은 존재하지 않습니다. 배송에서 오류가 났다고 이미 결제된 카드가 저절로 취소되지는 않습니다. 사가 패턴은 보상 작업으로 답합니다. 완료된 단계를 명시적으로 되돌리고(결제 환불, 예약 해제), 일시적 실패는 멱등성 키와 함께 재시도해 재시도된 결제가 이중 청구되지 않게 하며, 모든 단계에 타임아웃을 둡니다.
API 오케스트레이션은 언제 사용해야 합니까?
오케스트레이션이 없다면 클라이언트가 서로 의존하는 호출을 세 번 이상 해야 할 때, 단계에 순서와 공통 오류 처리가 필요할 때, 모바일 네트워크에서 잦은 왕복이 비쌀 때입니다. 단일 서비스 호출과 순수한 병렬 읽기에는 필요하지 않습니다. 이런 경우는 더 단순한 집계로 충분합니다.
워크플로 엔진이 필요합니까, 아니면 함수로 충분합니까?
짧고 동기적인 흐름에는 평범한 서버 측 함수로 충분합니다. 단계 몇 개, 몇 초 안에 끝나고, 실패하면 클라이언트에 오류를 반환하는 흐름입니다. 내구성 있는 워크플로 엔진은 흐름이 오래 실행되거나, 재시작을 견뎌야 하거나, 예약된 재시도와 사람의 승인 단계가 필요할 때 그 무게만큼의 값을 합니다.