본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 18. · 6 Views
외부 API 연동 에이전트 완벽 가이드
Lambda를 활용한 외부 API 연동 에이전트 구현부터 에러 처리, 타임아웃 관리, 테스트까지 실무에 바로 적용 가능한 완벽 가이드입니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다.
목차
1. 외부 API 연동 설계
어느 날 김개발 씨가 회사에서 새로운 미션을 받았습니다. "날씨 API를 연동해서 사용자에게 실시간 날씨 정보를 제공해야 해요." 그런데 문제가 있었습니다.
외부 API를 어떻게 안전하게 연동해야 할지 막막했던 것입니다.
외부 API 연동 설계란 다른 서비스의 데이터를 안전하고 효율적으로 가져오는 아키텍처를 구상하는 것입니다. 마치 두 회사가 안전하게 정보를 주고받기 위해 계약서를 작성하는 것처럼, API 연동도 명확한 규칙과 구조가 필요합니다.
제대로 설계하면 장애 상황에서도 서비스를 안정적으로 운영할 수 있습니다.
다음 코드를 살펴봅시다.
// API 연동 설계 구조
const apiConfig = {
// 기본 설정: 엔드포인트와 인증 정보
baseURL: 'https://api.weather.com',
apiKey: process.env.WEATHER_API_KEY,
timeout: 5000, // 5초 타임아웃
// 재시도 전략
retry: {
maxAttempts: 3,
backoffMultiplier: 2
},
// 에러 처리 정책
errorPolicy: 'fallback' // 실패 시 캐시 데이터 사용
};
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 오늘 팀장님으로부터 흥미로운 프로젝트를 배정받았습니다.
"우리 앱에 날씨 정보를 보여줘야 하는데, 외부 날씨 API를 연동해 주세요." 처음 받아본 API 연동 업무였습니다. 김개발 씨는 인터넷을 검색하며 코드를 작성하기 시작했습니다.
하지만 선배 개발자 박시니어 씨가 다가와 물었습니다. "설계는 했어요?
바로 코딩부터 시작하면 나중에 문제가 생길 수 있어요." 외부 API 연동 설계란 정확히 무엇일까요? 쉽게 비유하자면, API 연동 설계는 마치 해외여행 계획을 세우는 것과 같습니다.
어느 나라에 갈지, 비행기는 어떻게 탈지, 숙소는 어디로 할지, 문제가 생기면 어떻게 대처할지 미리 계획을 세우는 것입니다. 이처럼 API 연동도 어떤 API를 쓸지, 어떻게 호출할지, 실패하면 어떻게 할지 미리 정해야 합니다.
설계 없이 API를 연동하던 시절에는 어땠을까요? 개발자들은 각자 다른 방식으로 API를 호출했습니다.
A 개발자는 타임아웃을 10초로 설정하고, B 개발자는 30초로 설정했습니다. 에러가 발생하면 어떤 코드는 바로 실패 처리를, 어떤 코드는 재시도를 했습니다.
더 큰 문제는 일관성이 없어서 유지보수가 어려웠다는 점입니다. 바로 이런 문제를 해결하기 위해 통합된 API 연동 설계가 필요합니다.
설계를 제대로 하면 모든 팀원이 같은 방식으로 API를 연동할 수 있습니다. 또한 장애가 발생했을 때 어떻게 대응할지 명확한 규칙이 있어 빠르게 복구할 수 있습니다.
무엇보다 나중에 다른 API를 추가할 때도 같은 패턴을 재사용할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 baseURL과 apiKey는 어떤 API 서버에 접속할지, 인증은 어떻게 할지를 정의합니다. 이 부분이 가장 기본이 됩니다.
다음으로 timeout은 API 응답을 최대 몇 초까지 기다릴지 결정합니다. 5초 이상 응답이 없으면 실패로 처리하는 것입니다.
retry 설정은 실패했을 때 몇 번까지 재시도할지를 정의합니다. maxAttempts가 3이면 최대 3번까지 재시도합니다.
backoffMultiplier는 재시도 간격을 어떻게 늘릴지를 결정합니다. 2배씩 늘어나므로 첫 재시도는 1초 후, 두 번째는 2초 후, 세 번째는 4초 후에 실행됩니다.
마지막으로 errorPolicy는 모든 재시도가 실패했을 때 어떻게 할지를 정합니다. 'fallback'은 캐시된 이전 데이터를 사용하는 것을 의미합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 쇼핑몰 서비스를 개발한다고 가정해봅시다.
결제 API, 배송 조회 API, 재고 관리 API 등 여러 외부 API를 연동해야 합니다. 이때 위와 같은 설계 패턴을 공통으로 적용하면 일관성 있는 코드베이스를 유지할 수 있습니다.
많은 기업에서 이런 패턴을 적극적으로 사용하고 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 모든 API에 같은 타임아웃을 설정하는 것입니다. 결제 API처럼 느린 API도 있고, 검색 API처럼 빠른 API도 있습니다.
각 API의 특성에 맞게 타임아웃을 조정해야 합니다. 따라서 API별로 설정을 다르게 가져갈 수 있도록 설계해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 먼저 설계를 해야 하는 거군요!" API 연동 설계를 제대로 이해하면 더 안정적이고 유지보수하기 쉬운 시스템을 만들 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - API별로 다른 타임아웃과 재시도 정책을 적용하세요
- 민감한 정보는 반드시 환경 변수로 관리하세요
- 설계 문서를 팀과 공유하여 일관성을 유지하세요
2. API 호출 Lambda 구현
설계를 마친 김개발 씨는 이제 실제 코드를 작성할 차례입니다. "Lambda로 API를 호출하면 된다는데, 어떻게 시작해야 하지?" 선배에게 물어보니 "Lambda는 서버를 관리할 필요 없이 코드만 실행할 수 있어서 편리해요"라는 답변이 돌아왔습니다.
Lambda 함수는 AWS에서 제공하는 서버리스 컴퓨팅 서비스입니다. 마치 식당에서 요리만 주문하면 주방에서 알아서 만들어주는 것처럼, 개발자는 코드만 작성하면 AWS가 서버 관리를 대신해줍니다.
외부 API를 호출하는 Lambda 함수를 만들면 확장성과 안정성을 동시에 얻을 수 있습니다.
다음 코드를 살펴봅시다.
const axios = require('axios');
exports.handler = async (event) => {
try {
// API 호출 설정
const response = await axios.get('https://api.weather.com/v1/current', {
params: {
city: event.city || 'Seoul',
apikey: process.env.WEATHER_API_KEY
},
timeout: 5000
});
// 성공 응답
return {
statusCode: 200,
body: JSON.stringify(response.data)
};
} catch (error) {
// 에러 응답
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};
김개발 씨는 드디어 실제 코드를 작성하기 시작했습니다. 하지만 막상 시작하려니 막막했습니다.
"Lambda 함수는 어떻게 생겼지? 일반 Node.js 코드와 뭐가 다른 거지?" 박시니어 씨가 옆에 앉아 설명해주었습니다.
"Lambda 함수는 특별한 구조가 있어요. exports.handler라는 함수가 진입점이 됩니다." Lambda 함수란 정확히 무엇일까요?
쉽게 비유하자면, Lambda 함수는 마치 자동판매기와 같습니다. 동전을 넣고 버튼을 누르면 음료수가 나옵니다.
자동판매기 내부가 어떻게 작동하는지, 전기는 어떻게 공급되는지 신경 쓸 필요가 없습니다. 마찬가지로 Lambda도 요청이 들어오면 코드가 실행되고 결과를 반환합니다.
서버가 몇 대인지, 메모리는 어떻게 관리하는지 AWS가 알아서 처리해줍니다. Lambda가 없던 시절에는 어땠을까요?
개발자들은 EC2 인스턴스를 직접 관리해야 했습니다. 서버를 켜고, 보안 패치를 하고, 트래픽이 증가하면 서버를 추가해야 했습니다.
새벽 3시에 서버가 다운되면 일어나서 재부팅해야 하는 일도 있었습니다. 더 큰 문제는 트래픽이 없을 때도 서버 비용을 계속 내야 한다는 점이었습니다.
바로 이런 문제를 해결하기 위해 Lambda가 등장했습니다. Lambda를 사용하면 서버 관리에서 완전히 해방됩니다.
코드만 업로드하면 AWS가 모든 인프라를 관리해줍니다. 또한 사용한 만큼만 비용을 지불하므로 경제적입니다.
무엇보다 트래픽이 급증해도 자동으로 확장되어 안정적인 서비스를 제공할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 exports.handler는 Lambda 함수의 진입점입니다. 모든 Lambda 함수는 이 형식을 따라야 합니다.
event 매개변수에는 함수를 호출할 때 전달된 데이터가 들어있습니다. 예를 들어 도시 이름이 들어올 수 있습니다.
axios.get으로 실제 외부 API를 호출합니다. params에 쿼리 파라미터를 설정하고, timeout으로 최대 대기 시간을 지정합니다.
process.env.WEATHER_API_KEY는 환경 변수에서 API 키를 가져옵니다. 절대 코드에 직접 API 키를 적어서는 안 됩니다.
API 호출이 성공하면 statusCode 200과 함께 데이터를 반환합니다. Lambda 함수는 항상 이런 형식으로 응답해야 합니다.
실패하면 catch 블록에서 에러를 잡아 statusCode 500과 에러 메시지를 반환합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 온라인 서점 서비스를 개발한다고 가정해봅시다. 사용자가 책을 검색하면 Lambda 함수가 출판사 API를 호출하여 최신 재고 정보를 가져옵니다.
주문이 들어오면 또 다른 Lambda 함수가 결제 API를 호출합니다. 이렇게 각 기능별로 Lambda 함수를 나누면 관리가 쉬워집니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 Lambda 함수 안에서 무거운 작업을 하는 것입니다.
Lambda는 최대 15분까지만 실행할 수 있습니다. 대용량 파일을 처리하거나 복잡한 계산을 해야 한다면 다른 서비스를 고려해야 합니다.
따라서 Lambda는 빠르게 처리 가능한 작업에만 사용하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 Lambda 함수를 작성하고 테스트해보았습니다. "오, 진짜 동작하네요!" Lambda 함수를 제대로 이해하면 서버 관리 부담 없이 안정적인 API 연동 시스템을 만들 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 환경 변수를 활용하여 API 키를 안전하게 관리하세요
- Lambda 함수는 가볍고 빠른 작업에 적합합니다
- 코드를 배포하기 전에 반드시 로컬에서 테스트하세요
3. 에러 처리 전략
Lambda 함수를 배포한 김개발 씨는 기분이 좋았습니다. 하지만 다음 날 아침, 팀장님이 다가왔습니다.
"어제 새벽에 API가 다운됐는데, 우리 서비스까지 먹통이 됐어요. 에러 처리는 어떻게 했나요?" 김개발 씨는 아무 말도 할 수 없었습니다.
에러 처리 전략이란 외부 API가 실패했을 때 서비스가 중단되지 않도록 대비하는 방법입니다. 마치 비행기가 엔진 하나가 고장 나도 다른 엔진으로 안전하게 착륙할 수 있는 것처럼, API 연동에도 여러 안전장치가 필요합니다.
제대로 된 에러 처리가 있어야 사용자 경험을 보호할 수 있습니다.
다음 코드를 살펴봅시다.
class APIError extends Error {
constructor(message, statusCode, retryable = false) {
super(message);
this.statusCode = statusCode;
this.retryable = retryable; // 재시도 가능 여부
}
}
async function callWeatherAPI(city) {
try {
const response = await axios.get(`https://api.weather.com/v1/current`, {
params: { city, apikey: process.env.WEATHER_API_KEY }
});
return response.data;
} catch (error) {
// 네트워크 에러 - 재시도 가능
if (error.code === 'ECONNABORTED') {
throw new APIError('API timeout', 504, true);
}
// 서버 에러 - 재시도 가능
if (error.response?.status >= 500) {
throw new APIError('Server error', error.response.status, true);
}
// 클라이언트 에러 - 재시도 불가
throw new APIError('Client error', error.response?.status || 400, false);
}
}
김개발 씨는 충격을 받았습니다. 자신의 코드 때문에 서비스가 중단됐다니요.
박시니어 씨가 조용히 말했습니다. "외부 API는 언제든 실패할 수 있어요.
그걸 대비하는 게 에러 처리입니다." 점심시간에 박시니어 씨는 김개발 씨를 불러 자세히 설명해주었습니다. "에러에도 종류가 있어요.
어떤 에러는 재시도하면 해결되고, 어떤 에러는 재시도해도 소용없어요." 에러 처리 전략이란 정확히 무엇일까요? 쉽게 비유하자면, 에러 처리는 마치 응급 상황 대응 매뉴얼과 같습니다.
화재가 났을 때, 지진이 났을 때, 침수가 됐을 때 각각 다른 대응 방법이 필요합니다. 마찬가지로 API 에러도 종류에 따라 다르게 대응해야 합니다.
네트워크 에러인지, 서버 에러인지, 잘못된 요청인지에 따라 처리 방법이 달라집니다. 에러 처리 전략 없이 개발하던 시절에는 어땠을까요?
모든 에러를 똑같이 처리했습니다. API가 실패하면 무조건 사용자에게 "오류가 발생했습니다"라는 메시지만 보여줬습니다.
재시도하면 성공할 수 있는 일시적 에러도 바로 포기했고, 반대로 재시도해도 소용없는 에러를 계속 시도하여 리소스를 낭비했습니다. 더 큰 문제는 왜 에러가 발생했는지 로그도 제대로 남기지 않아 디버깅이 어려웠다는 점입니다.
바로 이런 문제를 해결하기 위해 체계적인 에러 처리 전략이 필요합니다. 에러를 종류별로 분류하면 적절한 대응이 가능해집니다.
일시적인 네트워크 에러는 재시도하고, 영구적인 에러는 빠르게 실패 처리하여 리소스를 절약합니다. 또한 의미 있는 에러 메시지를 사용자에게 전달할 수 있습니다.
무엇보다 로그를 통해 문제의 원인을 빠르게 파악하고 개선할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 APIError 클래스를 정의합니다. 이 클래스는 일반 Error를 확장하여 statusCode와 retryable 속성을 추가합니다.
retryable이 true면 재시도할 가치가 있는 에러라는 뜻입니다. callWeatherAPI 함수에서 실제 API를 호출합니다.
try-catch 블록으로 에러를 잡아냅니다. **error.code === 'ECONNABORTED'**는 타임아웃 에러입니다.
이건 네트워크가 느려서 생긴 일시적 문제일 수 있으므로 retryable: true로 설정합니다. error.response?.status >= 500은 서버 에러입니다.
500번대 에러는 서버 쪽 문제이므로 잠시 후 재시도하면 성공할 수 있습니다. 반면 400번대 에러는 우리가 잘못된 요청을 보낸 것이므로 재시도해도 소용없습니다.
retryable: false로 설정합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 배달 앱 서비스를 개발한다고 가정해봅시다. 사용자가 주문하면 음식점 API를 호출하여 주문을 접수합니다.
이때 음식점 서버가 일시적으로 과부하 상태라면 잠시 후 재시도하여 주문을 성공시킬 수 있습니다. 하지만 메뉴가 품절이라는 에러라면 재시도해도 소용없으므로 사용자에게 다른 메뉴를 선택하도록 안내합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 에러를 무한정 재시도하는 것입니다.
재시도에도 한계가 있어야 합니다. 보통 3번 정도 재시도하고 실패하면 포기하는 것이 좋습니다.
따라서 최대 재시도 횟수를 설정하고, 재시도 간격도 점진적으로 늘려야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 에러도 종류가 있고, 각각 다르게 대응해야 하는군요!" 에러 처리 전략을 제대로 이해하면 더 안정적이고 복원력 있는 시스템을 만들 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 에러를 종류별로 분류하여 재시도 여부를 결정하세요
- 최대 재시도 횟수를 설정하여 무한 루프를 방지하세요
- 의미 있는 에러 로그를 남겨 디버깅을 쉽게 하세요
4. 타임아웃 관리
에러 처리를 개선한 김개발 씨는 한결 마음이 놓였습니다. 그런데 며칠 후 또 다른 문제가 발생했습니다.
"API 응답이 30초씩 걸리다가 결국 실패해요. 사용자들이 너무 오래 기다리고 있어요." 박시니어 씨가 말했습니다.
"타임아웃 관리가 필요한 시점이네요."
타임아웃 관리란 외부 API가 응답하지 않을 때 무한정 기다리지 않고 적절한 시점에 포기하는 전략입니다. 마치 택시를 기다릴 때 30분이 지나면 다른 교통수단을 찾는 것처럼, API도 일정 시간 이상 기다리면 포기하고 대안을 찾아야 합니다.
적절한 타임아웃 설정이 사용자 경험을 크게 향상시킵니다.
다음 코드를 살펴봅시다.
const axios = require('axios');
// 타임아웃 설정을 포함한 API 호출
async function callAPIWithTimeout(url, options = {}) {
const controller = new AbortController();
// 타임아웃 설정: 5초
const timeoutId = setTimeout(() => {
controller.abort();
}, options.timeout || 5000);
try {
const response = await axios.get(url, {
...options,
signal: controller.signal,
timeout: options.timeout || 5000
});
clearTimeout(timeoutId); // 성공 시 타임아웃 해제
return response.data;
} catch (error) {
clearTimeout(timeoutId);
if (error.code === 'ECONNABORTED' || error.name === 'AbortError') {
throw new Error('API request timeout');
}
throw error;
}
}
김개발 씨는 모니터를 보며 한숨을 쉬었습니다. 사용자들이 로딩 화면을 30초씩 보고 있었습니다.
"왜 이렇게 오래 걸리는 거지?" 박시니어 씨가 코드를 보더니 문제를 발견했습니다. "타임아웃 설정이 없네요.
API가 응답하지 않으면 무한정 기다리고 있어요." 김개발 씨는 놀랐습니다. "타임아웃을 설정하지 않으면 계속 기다리는 거였나요?" 타임아웃 관리란 정확히 무엇일까요?
쉽게 비유하자면, 타임아웃 관리는 마치 전화 통화에서 벨이 울리는 시간을 제한하는 것과 같습니다. 상대방이 전화를 받지 않으면 30초 정도 벨이 울린 후 자동으로 끊깁니다.
만약 이런 제한이 없다면 상대방이 전화기를 영원히 안 들어도 계속 기다려야 합니다. API도 마찬가지입니다.
응답이 없으면 적절한 시점에 포기하고 다른 방법을 찾아야 합니다. 타임아웃 관리 없이 API를 호출하던 시절에는 어땠을까요?
외부 API 서버가 느려지거나 다운되면 우리 서비스까지 같이 멈췄습니다. 사용자들은 로딩 화면만 보면서 몇 분씩 기다려야 했습니다.
심한 경우 브라우저가 응답 없음 상태가 되어 강제 종료해야 했습니다. 더 큰 문제는 동시에 많은 요청이 타임아웃 없이 대기하면서 서버 리소스를 모두 소진한다는 점이었습니다.
바로 이런 문제를 해결하기 위해 체계적인 타임아웃 관리가 필요합니다. 타임아웃을 설정하면 일정 시간 내에 응답이 없으면 빠르게 실패 처리합니다.
사용자는 무한정 기다리지 않고 에러 메시지를 받아볼 수 있습니다. 또한 서버 리소스를 효율적으로 관리할 수 있습니다.
무엇보다 타임아웃 이후 폴백 데이터를 제공하거나 재시도 로직을 실행할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 AbortController를 생성합니다. 이것은 비동기 작업을 중단할 수 있는 표준 API입니다.
setTimeout으로 5초 후에 **controller.abort()**를 호출하도록 예약합니다. 5초가 지나면 API 요청이 강제로 취소됩니다.
axios.get에 signal 옵션을 전달합니다. 이것으로 AbortController와 연결됩니다.
timeout 옵션도 함께 설정하여 이중으로 보호합니다. API 호출이 성공하면 clearTimeout으로 예약된 타임아웃을 취소합니다.
catch 블록에서 에러를 확인합니다. error.code === 'ECONNABORTED' 또는 **error.name === 'AbortError'**면 타임아웃으로 인한 에러입니다.
이 경우 명확한 에러 메시지를 던집니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 여행 예약 서비스를 개발한다고 가정해봅시다. 항공권 검색 API를 호출할 때 타임아웃을 3초로 설정합니다.
3초 내에 응답이 없으면 "검색 시간이 초과되었습니다. 다시 시도해주세요"라는 메시지를 보여줍니다.
동시에 캐시된 이전 검색 결과를 보여주어 사용자가 완전히 빈 화면만 보지 않도록 합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 타임아웃을 너무 짧게 설정하는 것입니다. 네트워크가 조금만 느려져도 타임아웃이 발생하여 불필요한 재시도가 늘어납니다.
반대로 너무 길게 설정하면 사용자가 너무 오래 기다려야 합니다. 따라서 API의 평균 응답 시간을 측정하여 적절한 타임아웃을 찾아야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 도움으로 타임아웃을 설정한 김개발 씨는 테스트를 해보았습니다.
"오, 이제 5초 후에 바로 에러 처리되네요!" 타임아웃 관리를 제대로 이해하면 더 빠르고 안정적인 사용자 경험을 제공할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - API의 평균 응답 시간을 측정하여 적절한 타임아웃을 설정하세요
- 타임아웃 발생 시 사용자에게 명확한 메시지를 제공하세요
- 중요한 API는 타임아웃을 길게, 부가 기능은 짧게 설정하세요
5. 응답 가공하기
API 호출이 안정화된 김개발 씨는 이제 데이터를 사용자에게 보여줄 차례였습니다. 그런데 문제가 있었습니다.
"API 응답이 너무 복잡해요. 온도, 습도, 풍속 같은 데이터가 필요한데 불필요한 정보가 너무 많아요." 박시니어 씨가 조언했습니다.
"응답을 가공해서 필요한 것만 추출하세요."
응답 가공이란 외부 API로부터 받은 복잡한 데이터를 우리 서비스에 맞게 정리하고 변환하는 과정입니다. 마치 과일 주스를 만들 때 껍질과 씨를 제거하고 과육만 사용하는 것처럼, API 응답도 필요한 데이터만 추출하고 형식을 맞춰야 합니다.
깔끔하게 가공된 데이터는 프론트엔드 개발을 훨씬 쉽게 만듭니다.
다음 코드를 살펴봅시다.
// 원본 API 응답 예시
const rawWeatherResponse = {
coord: { lon: 126.9778, lat: 37.5683 },
weather: [{ id: 800, main: "Clear", description: "clear sky" }],
main: { temp: 15.5, feels_like: 14.2, humidity: 65 },
wind: { speed: 3.5, deg: 180 },
sys: { country: "KR", sunrise: 1638835200, sunset: 1638871800 }
};
// 응답 가공 함수
function transformWeatherData(rawData) {
return {
// 필요한 데이터만 추출
temperature: Math.round(rawData.main.temp), // 반올림
feelsLike: Math.round(rawData.main.feels_like),
humidity: rawData.main.humidity,
windSpeed: rawData.wind.speed,
condition: rawData.weather[0].description,
// 추가 가공: 섭씨를 화씨로 변환
temperatureF: Math.round(rawData.main.temp * 9/5 + 32),
// 사용자 친화적 형식
displayText: `${Math.round(rawData.main.temp)}°C, ${rawData.weather[0].description}`
};
}
김개발 씨는 API 응답을 보며 머리가 아팠습니다. 날씨 정보를 가져오긴 했는데, JSON 구조가 너무 복잡했습니다.
"coord, sys, weather 배열... 이걸 어떻게 화면에 보여주지?" 박시니어 씨가 화면을 보더니 웃었습니다.
"외부 API는 보통 많은 정보를 줘요. 하지만 우리가 필요한 건 온도, 습도, 날씨 상태 정도잖아요?
필요한 것만 뽑아내세요." 응답 가공이란 정확히 무엇일까요? 쉽게 비유하자면, 응답 가공은 마치 도서관에서 필요한 책만 골라내는 것과 같습니다.
도서관에는 수만 권의 책이 있지만, 우리는 필요한 책 몇 권만 빌립니다. 마찬가지로 API는 수십 개의 필드를 주지만, 우리는 실제로 사용할 몇 개만 선택하여 깔끔한 형태로 정리합니다.
이렇게 하면 코드가 간결해지고 유지보수가 쉬워집니다. 응답 가공 없이 개발하던 시절에는 어땠을까요?
프론트엔드 코드에서 매번 복잡한 경로로 데이터를 접근해야 했습니다. response.main.temp, response.weather[0].description 같은 긴 경로를 여러 곳에서 반복했습니다.
구조가 바뀌면 모든 코드를 수정해야 했습니다. 더 큰 문제는 섭씨를 화씨로 변환하거나 날짜 형식을 바꾸는 로직이 곳곳에 흩어져 있어 일관성이 없었다는 점입니다.
바로 이런 문제를 해결하기 위해 중앙화된 응답 가공이 필요합니다. 응답 가공 함수를 만들면 한 곳에서 데이터 구조를 정리할 수 있습니다.
외부 API 구조가 바뀌어도 가공 함수만 수정하면 됩니다. 또한 단위 변환이나 날짜 포맷팅 같은 로직을 한 곳에 모아 일관성을 유지할 수 있습니다.
무엇보다 프론트엔드 개발자가 간단한 인터페이스로 데이터를 사용할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 rawWeatherResponse는 실제 날씨 API가 반환하는 복잡한 구조입니다. coord, sys 같은 우리에게 불필요한 정보가 많이 포함되어 있습니다.
transformWeatherData 함수가 응답을 가공합니다. rawData.main.temp에서 온도를 추출하고 Math.round로 반올림합니다.
소수점이 있는 온도보다 정수가 보기 편하기 때문입니다. temperatureF는 섭씨를 화씨로 변환한 값입니다.
미국 사용자를 위해 추가했습니다. 변환 공식은 **(섭씨 * 9/5 + 32)**입니다.
displayText는 사용자에게 바로 보여줄 수 있는 형식으로 조합한 문자열입니다. 결과적으로 복잡한 원본 데이터가 temperature, humidity, condition 같은 간단한 필드로 정리됩니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 쇼핑몰 상품 검색 서비스를 개발한다고 가정해봅시다.
외부 상품 API는 제조사 코드, 바코드, 재고 위치 같은 수십 개 필드를 반환합니다. 하지만 우리가 화면에 보여줄 건 상품명, 가격, 이미지 정도입니다.
가공 함수에서 이 세 가지만 추출하고, 가격에 콤마를 찍어 포맷팅합니다. 프론트엔드 개발자는 product.name, product.price만 사용하면 됩니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 너무 많은 가공을 하는 것입니다.
나중에 필요할지도 모른다고 모든 필드를 다 변환하려고 합니다. 이렇게 하면 코드가 복잡해지고 성능도 떨어집니다.
따라서 현재 실제로 사용하는 데이터만 가공하고, 필요할 때 추가하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 조언대로 응답 가공 함수를 만든 김개발 씨는 프론트엔드 코드를 보며 만족스러워했습니다. "훨씬 깔끔해졌어요!" 응답 가공을 제대로 이해하면 더 유지보수하기 쉽고 확장 가능한 코드를 작성할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 실제로 사용하는 필드만 추출하여 불필요한 데이터를 제거하세요
- 단위 변환이나 포맷팅은 한 곳에 모아 일관성을 유지하세요
- API 응답 구조가 바뀌어도 가공 함수만 수정하면 되도록 설계하세요
6. 테스트 및 디버깅
모든 기능을 구현한 김개발 씨는 자신감이 넘쳤습니다. 하지만 배포 전날, 박시니어 씨가 물었습니다.
"테스트는 했어요? 실제 API 서버가 다운됐을 때도 제대로 동작하나요?" 김개발 씨는 당황했습니다.
"그건... 어떻게 테스트하죠?"
테스트 및 디버깅이란 API 연동 코드가 정상 상황뿐만 아니라 비정상 상황에서도 올바르게 동작하는지 검증하는 과정입니다. 마치 자동차 출고 전에 브레이크, 에어백 등 안전장치를 철저히 점검하는 것처럼, 코드도 배포 전에 다양한 시나리오를 테스트해야 합니다.
체계적인 테스트가 서비스 안정성을 보장합니다.
다음 코드를 살펴봅시다.
// Jest를 사용한 API 연동 테스트
const axios = require('axios');
const { callWeatherAPI } = require('./weatherService');
jest.mock('axios'); // axios를 모킹
describe('Weather API Tests', () => {
// 정상 케이스 테스트
test('should return weather data on success', async () => {
const mockData = { main: { temp: 15.5, humidity: 65 } };
axios.get.mockResolvedValue({ data: mockData });
const result = await callWeatherAPI('Seoul');
expect(result.temperature).toBe(16); // 반올림 확인
});
// 타임아웃 테스트
test('should throw timeout error', async () => {
axios.get.mockRejectedValue({ code: 'ECONNABORTED' });
await expect(callWeatherAPI('Seoul'))
.rejects.toThrow('API timeout');
});
// 서버 에러 테스트
test('should handle 500 error', async () => {
axios.get.mockRejectedValue({ response: { status: 500 } });
await expect(callWeatherAPI('Seoul'))
.rejects.toThrow('Server error');
});
});
김개발 씨는 난감했습니다. "API 서버를 제가 직접 다운시킬 수는 없는데, 어떻게 테스트하죠?" 박시니어 씨가 웃으며 말했습니다.
"모킹이라는 기법을 사용하면 돼요. 실제 API를 호출하지 않고도 다양한 상황을 시뮬레이션할 수 있어요." 다음 날 오전, 박시니어 씨는 김개발 씨에게 테스트 코드 작성법을 가르쳐주었습니다.
테스트 및 디버깅이란 정확히 무엇일까요? 쉽게 비유하자면, 테스트는 마치 소방 훈련과 같습니다.
실제 화재가 나기 전에 비상구를 찾고, 소화기 사용법을 익히고, 대피 동선을 확인합니다. 마찬가지로 코드 테스트도 실제 장애가 발생하기 전에 다양한 상황을 시뮬레이션하여 문제를 미리 발견하고 수정합니다.
이렇게 하면 실제 서비스에서 예상치 못한 에러를 크게 줄일 수 있습니다. 테스트 없이 개발하던 시절에는 어땠을까요?
개발자들은 코드를 작성하고 바로 배포했습니다. 문제는 배포 후에 발견됐습니다.
사용자들이 에러를 경험하고 나서야 버그를 알게 됐습니다. 급하게 수정하고 다시 배포하는 일이 반복됐습니다.
더 큰 문제는 한 부분을 고쳤더니 다른 부분이 망가지는 사이드 이펙트를 발견하기 어려웠다는 점입니다. 바로 이런 문제를 해결하기 위해 자동화된 테스트가 필요합니다.
테스트 코드를 작성하면 코드 변경 후 즉시 모든 기능이 정상 동작하는지 확인할 수 있습니다. 에러 상황도 안전하게 시뮬레이션하여 에러 처리 로직이 제대로 작동하는지 검증합니다.
또한 리팩토링할 때 기존 기능이 망가지지 않았는지 빠르게 확인할 수 있습니다. 무엇보다 새로운 팀원이 코드를 수정해도 테스트가 안전망 역할을 한다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 **jest.mock('axios')**로 axios 라이브러리를 모킹합니다.
이제 실제 네트워크 요청 없이 테스트할 수 있습니다. describe 블록으로 관련 테스트들을 그룹화합니다.
첫 번째 테스트는 정상 케이스입니다. mockResolvedValue로 성공 응답을 시뮬레이션합니다.
callWeatherAPI를 호출하고, 온도가 제대로 반올림됐는지 expect로 검증합니다. 두 번째 테스트는 타임아웃 케이스입니다.
mockRejectedValue로 타임아웃 에러를 시뮬레이션합니다. 함수가 'API timeout' 에러를 던지는지 확인합니다.
세 번째 테스트는 서버 에러 케이스입니다. 500번 에러를 시뮬레이션하고, 적절한 에러 메시지가 나오는지 검증합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 결제 시스템을 개발한다고 가정해봅시다.
결제 API 연동 코드를 작성한 후 다양한 테스트를 합니다. 정상 결제, 잔액 부족, 카드 정보 오류, 네트워크 타임아웃 등 모든 시나리오를 테스트합니다.
CI/CD 파이프라인에 테스트를 통합하여 코드가 푸시될 때마다 자동으로 검증합니다. 이렇게 하면 결제 기능에 문제가 생기는 것을 사전에 방지할 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 정상 케이스만 테스트하는 것입니다.
하지만 실제 문제는 비정상 케이스에서 발생합니다. API 타임아웃, 서버 에러, 잘못된 데이터 형식 등 다양한 실패 시나리오를 테스트해야 합니다.
따라서 정상 케이스 1개당 에러 케이스 2-3개를 작성하는 것이 좋습니다. 디버깅할 때는 로그를 적극 활용해야 합니다.
API 요청과 응답을 로그로 남기면 문제를 빠르게 찾을 수 있습니다. AWS CloudWatch나 Datadog 같은 모니터링 도구를 사용하면 실시간으로 에러를 추적할 수 있습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 테스트 코드를 작성한 김개발 씨는 자신감이 생겼습니다.
"이제 배포해도 안심이에요!" 박시니어 씨가 웃으며 말했습니다. "좋아요.
하지만 테스트는 한 번 작성하고 끝이 아니에요. 코드가 변경될 때마다 계속 실행해야 합니다." 테스트 및 디버깅을 제대로 이해하면 더 안정적이고 신뢰할 수 있는 시스템을 만들 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - 정상 케이스보다 에러 케이스를 더 많이 테스트하세요
- 모킹을 활용하여 외부 의존성 없이 테스트하세요
- CI/CD 파이프라인에 테스트를 통합하여 자동화하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
EFK 스택 로깅 완벽 가이드
마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
분산 추적 완벽 가이드
마이크로서비스 환경에서 요청의 전체 흐름을 추적하는 분산 추적 시스템의 핵심 개념을 배웁니다. Trace, Span, Trace ID 전파, 샘플링 전략까지 실무에 필요한 모든 것을 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.