1. 왜 Kakao API를 썼는가?
오먹추 프로젝트의 목표는 사용자의 음식 선택을 도와주는 추천 기능을 만드는 것이었다.
이때 가장 중요했던 건 장소 정보였다.
예를 들어 사용자들이 '김밥', '떡볶이'처럼 투표한 메뉴를 기반으로 사용자 주변에 있는 관련 식당들을 알려줘야 했다.
그래서 생각났던 것이 Kakao Maps였는데 한국 기반 서비스 + 개발 문서가 깔끔 + 손쉬운 REST API를 제공 해서 선택을 하게 되었다.
2. 연동을 위한 사전 준비
개발자 등록 & 키 발급
- Kakao Devlopers 접속
- 로그인 후 내 애플리케인션에서 애플리케이션 추가하기
- 왼쪽 대시보드에 앱 설정 -> 앱 키에 들어가서 REST API 키 사용
3. Kakao API 호출을 위한 Client 클래스
일단 Kakao Maps의 키워드로 장소 검색하는 문서를 살펴보자!



문서에서는 다음 정보를 제공한다.
- 요청 URL
GET "https://dapi.kakao.com/v2/local/search/keyword.json";
- 쿼리 파라미터 목록
- query : 검색어
- x, y : 중심 좌표
- radius : 반경 (미터 단위, 최대 20,000)
- 헤더 설정
Authorization: KakaoAK {REST_API_KEY}
근데 여기까지만 보면
String url = "https://dapi.kakao.com/v2/local/search/keyword.json?query=분식&x=...&y=...";
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + apiKey);
이 정도까지만 감이 오지, 어떤 JSON이 오고 어떻게 매핑해야하는지는 나오지 않는다.
그래서 문서 밑에 cURL로 요청해서 테스트한 응답 구조가 나온다.
curl -X GET "https://dapi.kakao.com/v2/local/search/keyword.json?query=분식&x=126.978&y=37.5665&radius=2000" \
-H "Authorization: KakaoAK {YOUR_REST_API_KEY}"
그러면 JSON 응답이 아래와 같이 떨어진다.


@Component
@RequiredArgsConstructor
@Slf4j
public class KakaoClient {
@Value("${kakao.rest.api-key}")
private String kakaoApiKey;
private final RestTemplate restTemplate;
private static final String KAKAO_LOCAL_URL = "https://dapi.kakao.com/v2/local/search/keyword.json";
public List<PlaceDto> searchPlaces(String keyword, double lat, double lng, int radius) {
String url = String.format(
KAKAO_LOCAL_URL + "?query=%s&y=%f&x=%f&radius=%d",
keyword, lat, lng, radius
);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + kakaoApiKey);
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<KakaoSearchResponse> response = restTemplate.exchange(
url,
HttpMethod.GET,
entity,
KakaoSearchResponse.class
);
List<KakaoDocument> docs = response.getBody().getDocuments();
for (KakaoDocument doc : docs) {
log.info("가게 이름: {}, 거리 : {}, 주소 : {}",
doc.getPlaceName(), doc.getDistance(), doc.getAddress());
}
return docs.stream()
.map(PlaceDto::from)
.toList();
}
}
나머지 코드도 정리해보자.
- RestTemplate는 HTTP 요청을 자바 코드로 보낼 수 있는 도구
- 현재 하고 싶은 건? ➡️ 카카오 장소 검색 API에 GET 요청을 보내서 JSON 응답을 받아 오는 것
1) HttpEntity<Void> entity = new HttpEntity<>(headers);
- 역할 : 이 코드는 HTTP 요청을 생성할 때 사용된다. HttpEntity는 HTTP 요청 또는 응답에 해당하는 헤더와 바디를 포함하는 클래스이다. 여기서는 headers 객체를 포함하여 entity를 생성하였으며 바디는 없으므로 제네릭 타입으로 Void를 사용했다.
- 이유 : Kakao API는 요청 시 인증을 위한 헤더를 필요로 한다. 따라서 Authrozation 헤더를 포함한 httpHeaders 객체를 생성하고 이를 HttpEntity에 담아 사용한다.

2) restTemplate.exchange(...)
- 역할 : exchange() 메서드는 지정된 HTTP 메서드를 사용하여 주어진 URL에 요청을 보내고, 응답을 ResponseEntity로 반환한다. 여기서는 GET 메서드를 사용하여 KakaoAPI에 요청을 보내고, 응답을 KakaoSearchResponse 타입으로 받는다.
- 이유 : exchange() 메서드는 다양한 HTTP 메서드를 지원하며, 요청과 응답에 대한 세부적인 설정이 가능하다. 이를 통해 요청 헤더를 설정하고, 응답을 원하는 타입으로 변환할 수 있다.

3) List<KakaoDocument> docs = response.getBody().getDocumnets();
- 역할 : 응답 본문에서 documents 리스트를 추출한다. response.getBody()는 KakaoSearchResponse 객체를 반환하며, getDocuments()를 통해 실제 데이터 리스트를 가져온다
- 이유 : KakaoAPI의 응답은 documents라는 키 아래에 검색 결과를 담고 있다. 따라서 이를 추출하여 이후의 로직에서 활용한다.

4) return docs.stream().map(PlaceDtp::from).toList();
- 역할 : docs 리스트의 각 KakaoDocument 객체를 PlaceDto 객체로 변환한 후, 리스트로 수집하여 반환한다.
4. Kakao API 연동 시 사용된 DTO 클래스
그래서 위 cURL의 응답 문서 바탕으로 DTO를 구성했다.
프로젝트에서는 응답 값으로 받은 docmuents에서 몇개의 정보만 활용하려고 한다.
1) KakaoSearchResponse - 카카오 응답 전체를 담는 최상위 컨테이너
@Getter
@NoArgsConstructor
public class KakaoSearchResponse {
private List<KakaoDocument> documents;
}
❓이 클래스는 왜 존재할까?
- 카카오 API에서 검색 결과를 받을 때
{
"documents": [ ... ],
"meta": { ... }
}
이런식으로 documents라는 Key 아래 실제 장소 리스트들이 들어가 있다.
그래서 이 documents 키와 매핑되는 리스트가 필요하고, 그걸 담은게 바로 KakaoSearchResponse 클래스이다.
2) KakaoDocument - 장소 하나하나의 정보를 담는 클래스
@Getter
@NoArgsConstructor
public class KakaoDocument {
@JsonProperty("place_name")
private String placeName;
@JsonProperty("address_name")
private String address;
@JsonProperty("phone")
private String phone;
@JsonProperty("distance")
private String distance;
@JsonProperty("place_url")
private String placeUrl;
}
❓이 클래스는 왜 존재할까?
- 아까 말한 documents 배열 안에는 여러 장소의 정보가 있다.
- 각각의 장소가 JSON 객체로 묶여있고 그 하나의 장소 단위를 그대로 자바 객체로 옮긴 게 이 KakaoDocument 클래스이다.
- 예를 들어 밑의 JSON 하나가
{
"place_name": "김밥천국",
"address_name": "서울 중구 어딘가",
"phone": "02-1234-5678",
...
}
➡️ 바로 하나의 KakaoDocument 객체가 되는거다. 즉, 이 클래스는 장소 하나의 원본 데이터를 담당한다.
3) PlaceDto - 서비스 내부에서 사용할 가공된 응답 클래스
@Getter
@AllArgsConstructor
public class PlaceDto {
private String name;
private String address;
private String phone;
private String distance;
private String uri;
public static PlaceDto from(KakaoDocument document) {
return new PlaceDto(
document.getPlaceName(),
document.getAddress(),
document.getPhone(),
document.getDistance(),
document.getPlaceUrl()
);
}
}
❓ 얘는 왜 또 따로 있는 걸까?
- 우리가 클라이언트(프론트엔드 등)에게 그대로 KakaoDocument를 넘겨도 되지만 서비스의 응답 형태는 서비스에 맞게 정의하는게 좋으므로 PlaceDto는 응답에 꼭 필요한 필드만 골라 담아서 API 응답 전용 객체로 사용하는 것이다.
5. 클래스 간 흐름 정리
카카오 API 응답 (JSON)
↓
KakaoSearchResponse ← documents → List<KakaoDocument>
↓
docs.stream().map(PlaceDto::from)
↓
List<PlaceDto> (최종 사용자 응답)'Project' 카테고리의 다른 글
| 복합 유니크 제약 조건의 필요성 (0) | 2025.04.04 |
|---|---|
| Spring Security와 JWT로 로그인 구현하기 (0) | 2025.04.03 |
| 스프링 프로젝트에서 공통 응답 + 페이징 + 전역 예외처리 (0) | 2025.03.30 |
| 결제 시나리오 3. 악의적 선점 - 데이터 정합성 문제(2) (0) | 2025.03.17 |
| 결제 시나리오 3. 악의적 선점 - 데이터 정합성 문제(1) (0) | 2025.03.17 |