외부 API 직접 호출의 문제점
CAP for Node.js 애플리케이션에서 외부 서비스(S/4HANA, 써드파티 API 등)를 호출할 때, 외부 서비스가 느려지거나 다운되면 CAP 앱 전체가 응답 불능에 빠질 수 있습니다. Circuit Breaker 패턴은 외부 서비스 장애가 내 앱으로 전파되는 것을 막는 안전장치입니다.
Circuit Breaker 없는 직접 호출 — 위험한 패턴
// 외부 API 직접 호출 — 외부 서비스 장애 시 내 앱도 블로킹
this.on('getWeatherForecast', async (req) => {
const { cityCode } = req.data;
// 외부 날씨 API가 10초 타임아웃이면 이 요청도 10초 블로킹
const response = await fetch(
`https://api.weather-service.com/forecast/${cityCode}`,
{ headers: { 'Authorization': `Bearer ${process.env.WEATHER_API_KEY}` } }
);
if (!response.ok) {
req.error(response.status, '날씨 데이터 조회 실패');
return;
}
return response.json();
});
opossum으로 Circuit Breaker 구현
// npm install opossum
const CircuitBreaker = require('opossum');
// 외부 API 호출 함수
async function fetchWeather(cityCode) {
const response = await fetch(
`https://api.weather-service.com/forecast/${cityCode}`,
{
headers: { 'Authorization': `Bearer ${process.env.WEATHER_API_KEY}` },
signal: AbortSignal.timeout(5000) // 5초 타임아웃
}
);
if (!response.ok) {
throw new Error(`Weather API 오류: ${response.status}`);
}
return response.json();
}
// Circuit Breaker 설정
const weatherBreaker = new CircuitBreaker(fetchWeather, {
timeout: 5000, // 5초 이상이면 실패 처리
errorThresholdPercentage: 50, // 50% 이상 실패 시 회로 차단
resetTimeout: 30000, // 30초 후 Half-Open 상태로 전환
volumeThreshold: 5 // 최소 5번 호출 후 임계값 적용
});
// CAP 핸들러에서 사용
this.on('getWeatherForecast', async (req) => {
const { cityCode } = req.data;
try {
const data = await weatherBreaker.fire(cityCode);
return data;
} catch (err) {
if (weatherBreaker.opened) {
// 회로가 열린 상태 — 빠른 실패
return req.error(503, '날씨 서비스 일시적 장애. 잠시 후 다시 시도하세요.');
}
return req.error(500, `날씨 데이터 조회 실패: ${err.message}`);
}
});
Circuit Breaker 상태와 폴백 처리
// 상태별 폴백 처리
weatherBreaker.fallback((cityCode) => {
// 회로 차단 시 캐시된 데이터 반환
return getCachedWeather(cityCode) || {
cityCode,
temperature: null,
description: '날씨 정보를 일시적으로 사용할 수 없습니다.',
cached: true
};
});
// Circuit Breaker 이벤트 모니터링
weatherBreaker.on('open', () => {
console.warn('[Circuit Breaker] 날씨 서비스 회로 차단 — 요청 차단 시작');
});
weatherBreaker.on('halfOpen', () => {
console.info('[Circuit Breaker] 날씨 서비스 회로 반개 — 테스트 요청 허용');
});
weatherBreaker.on('close', () => {
console.info('[Circuit Breaker] 날씨 서비스 회로 복구 — 정상 운영');
});
weatherBreaker.on('fallback', (result) => {
console.warn('[Circuit Breaker] 폴백 응답 사용:', result);
});
CAP Destination 서비스와 Circuit Breaker 조합
const CircuitBreaker = require('opossum');
const cds = require('@sap/cds');
// CAP Destination 호출을 Circuit Breaker로 감싸기
async function callS4HANA(entityName, filter) {
const S4 = await cds.connect.to('S4HANA_PROD');
return S4.run(SELECT.from(entityName).where(filter));
}
const s4Breaker = new CircuitBreaker(callS4HANA, {
timeout: 10000,
errorThresholdPercentage: 30,
resetTimeout: 60000
});
this.on('getSalesOrders', async (req) => {
try {
const orders = await s4Breaker.fire(
'A_SalesOrder',
{ SoldToParty: req.data.customerId }
);
return orders;
} catch (err) {
if (s4Breaker.opened) {
// S/4HANA 장애 시 로컬 캐시에서 반환
return getLocalOrderCache(req.data.customerId);
}
throw err;
}
});
Circuit Breaker 3가지 상태
- Closed (정상): 모든 요청이 외부 서비스로 전달됨. 실패율이 임계값 미만.
- Open (차단): 모든 요청이 즉시 실패 처리됨. 폴백 실행. 외부 서비스 보호.
- Half-Open (테스트): resetTimeout 후 일부 요청만 외부로 전달. 복구 여부 확인.
공식 문서
opossum 라이브러리 전체 옵션은 nodeshift.dev/opossum에서 확인하세요. CAP 외부 서비스 연동은 cap.cloud.sap/docs/guides/using-services를 참고하세요.
댓글 0
아직 댓글이 없습니다.