배달 앱에서 고객이 치킨을 '결제'했다고 상상해 봅시다. 백엔드 시스템 내부에서는 어마어마한 일들이 순차적으로 벌어집니다.
1. 주문 시스템에 접수
2. 라이더 배차 시스템 호출
3. 고객에게 카카오톡 알림 발송
4. 쿠폰 소진 처리
만약 결제 서버가 이 4개의 서버에게 일일이 HTTP(REST API)로 전화를 걸어 "야, 결제됐어! 일해!"라고 동기식(Synchronous)으로 통신하면 어떻게 될까요? 만약 카카오톡 알림 서버가 장애로 3초간 멈춰있다면, 결제 서버도 그 3초 동안 응답을 기다리며 멈춰있게 되고 결국 사용자의 화면은 하얗게 굳어버립니다. 하나의 서버 장애가 전체 시스템의 장애로 퍼지는 '도미노 붕괴(강한 결합)' 현상입니다. 이 끔찍한 의존성을 칼같이 끊어내기 위해 도입된 구원자가 바로 아파치 카프카(Apache Kafka)와 메시지 큐(Message Queue) 아키텍처입니다.
.
1. 카프카(Kafka) 핵심 용어 3총사: 토픽, 프로듀서, 컨슈머
카프카는 거대한 '비동기 우체국'입니다. HTTP(REST) 방식이 상대방에게 직접 '전화'를 거는 것이라면, 카프카는 상대방의 우체통에 '편지(Message)'를 던져놓고 나는 내 할 일을 하러 가는 방식입니다.
- Topic (토픽 = 우체통): 카프카 내부에 만들어지는 데이터를 분류하는 논리적 방(우체통)입니다. 예:
payment-create-topic - Producer (생산자 = 편지 보내는 놈): 메시지(데이터)를 만들어서 토픽에 쏘아 보내는(Publish) 서버입니다. (예: 결제 완료 서버)
- Consumer (소비자 = 편지 읽는 놈): 토픽에 웅크리고 대기하다가 메시지가 들어오면 즉시 꺼내서 읽고 처리하는(Subscribe) 서버입니다. (예: 알림 서버, 주문 서버)
가장 강력한 점은 알림 서버가 죽어있더라도, 결제 서버는 알 바 없이 카프카에 메시지를 쏠 수 있다는 것입니다. 카프카가 메시지를 안전하게 디스크에 보관하고 있다가, 알림 서버가 고쳐져서 다시 살아나면 그때 밀려있던 메시지들을 와다다 꺼내어 처리하게 됩니다. 완벽한 '결합도 낮춤(Decoupling)'과 '안전성'이 확보됩니다.
2. Spring Boot 프로듀서(Producer) 세팅: 메시지 쏘기
스프링 부트는 카프카와의 통신을 위해 아주 추상화가 잘 된 KafkaTemplate이라는 무기를 제공합니다.
// 의존성 추가: implementation 'org.springframework.kafka:spring-kafka'
@Service
@RequiredArgsConstructor
public class PaymentProducer {
// 스프링이 제공하는 카프카 통신 템플릿
private final KafkaTemplate<String, String> kafkaTemplate;
// 카프카의 방(우체통) 이름 지정
private static final String TOPIC = "payment-success-topic";
public void completePayment(Long orderId, int amount) {
// 1. DB에 결제 완료 저장... (빠르게 처리)
// 2. 다른 서버들을 기다리지 않고 카프카 우체통에 "결제됐어!" 라는 메시지(JSON)만 툭 던지고 퇴근합니다!
String message = String.format("{\"orderId\":%d, \"amount\":%d}", orderId, amount);
kafkaTemplate.send(TOPIC, message);
log.info("카프카 토픽으로 비동기 메시지 전송 완료: {}", message);
}
}
3. Spring Boot 컨슈머(Consumer) 세팅: 메시지 듣기
이제 메시지를 받아먹을 '알림 서버(Notification Server)'의 코드를 보겠습니다. @KafkaListener 어노테이션 하나만 달아두면, 메시지가 우체통에 들어오는 순간 스프링이 귀신같이 알아채고 이 메서드를 팍! 하고 실행시켜 줍니다.
@Service
@Slf4j
public class NotificationConsumer {
// 이 어노테이션이 붙은 메서드는 24시간 내내 해당 토픽을 감시(Listen)하고 있습니다.
// groupId는 컨슈머들의 소속 그룹을 의미하며, 메시지를 중복해서 처리하지 않도록(분산 처리) 도와주는 핵심 키입니다.
@KafkaListener(topics = "payment-success-topic", groupId = "notification-group")
public void consumePaymentMessage(String message) {
log.info(" 카프카로부터 새 메시지 수신: {}", message);
// JSON 파싱 후 유저에게 카카오톡 발송 로직 처리...
// 3초가 걸려도, 결제 서버에게는 전혀 영향을 주지 않습니다! (비동기의 마법)
}
}
4. 마무리 및 주요 발전 방향
지금까지 하나의 장애가 전체 서버를 터뜨리는 REST API 동기 통신의 병목 한계를 극복하고, '내가 할 일만 하고 편지는 우체통에 던져버린다'는 이벤트 주도(Event-Driven) 마이크로서비스 생태계의 절대 대동맥, Apache Kafka와 Spring Boot 연동의 원리에 대해 다루어 보았습니다. 이제 여러분의 서버 아키텍처는 어떠한 폭우(트래픽) 속에서도 유연하게 견디는 진정한 MSA로 진화했습니다.
서버 내부 아키텍처는 완벽하게 짰습니다. 그런데 이제 협업을 해야 할 프론트엔드 개발자가 다가와 묻습니다. "백엔드 개발자님, 회원가입 API 주소는 뭐고, 파라미터는 뭘 넘겨야 하죠? 리턴 JSON 양식 좀 워드나 엑셀 파일로 정리해서 주실래요?" 만약 코드가 수정될 때마다 엑셀 문서를 수동으로 고치고 있다면 엄청난 노가다와 휴먼 에러가 발생합니다. 코드와 문서가 일치하지 않아 프론트엔드와 매일 멱살을 잡고 싸워야 하죠. 본 설명에서는 "코드를 짜면 그 즉시 화려하고 인터랙티브(Interactive)한 API 문서 웹페이지가 1초 만에 자동 생성된다!" 백엔드 개발자의 퇴근을 앞당기는 기적의 툴, Swagger(Springdoc)와 REST Docs의 완벽 연동 실무 노하우에 대해 아주 심도 있게 살펴보겠습니다.
백엔드 개발자가 API 100개를 열심히 다 만들었습니다. 이제 이 API를 가져다 써야 하는 프론트엔드(React, iOS) 개발자와 핏대 높여 싸울 시간이 왔습니다. "회원가입 파라미터가 userName인가요 user_name인가요?", "에러가 났을 때 리턴되는 JSON 응답 양식 좀 엑셀로 정리해서 주세요!" 과거에는 노션(Notion)이나 엑셀에 한 땀 한 땀 API 스펙을 수동으로 적어주었습니다. 하지만 백엔드 코드가 수정될 때 문서 업데이트를 깜빡하는 순간, 코드는 최신인데 문서는 구버전인 '거짓말 문서'가 되어버리고 프론트엔드의 비난을 한 몸에 받게 됩니다. 이 끔찍한 비효율과 커뮤니케이션 비용을 제로(0)로 만들기 위해 등장한 구원자가 바로 "자바 코드를 읽어서 자동으로 멋진 웹페이지 API 문서를 만들어주는 도구"입니다.
이번에는 전 세계 양대 산맥으로 꼽히는 Swagger(Springdoc OpenAPI)와 Spring REST Docs의 극명한 차이점 비교와 실무 적용 가이드에 대하여 다뤄보겠습니다.
1. Swagger (Springdoc OpenAPI): 쉽고 강력하고 화려하다
Swagger(스웨거)는 현재 전 세계에서 가장 대중적으로 쓰이는 API 문서화 도구입니다. 과거 Springfox 라이브러리가 유행했으나 업데이트가 끊겨, Spring Boot 3.x 시대에는 무조건 'Springdoc OpenAPI'를 사용해야 합니다.
장점:
- 적용이 미친 듯이 쉽습니다. 의존성 한 줄만 넣고 서버 켜면 /swagger-ui.html 주소에 문서가 뚝딱 만들어집니다.
- [Try it out] 이라는 버튼을 제공해서, 프론트엔드 개발자가 포스트맨(Postman)을 안 켜고도 문서 페이지 안에서 직접 파라미터를 넣고 API를 호출하며 테스트해 볼 수 있습니다. (압도적 장점)
단점 (치명적):
문서를 예쁘게 꾸미려면 컨트롤러 코드에 Swagger 전용 어노테이션(@Operation, @ApiResponse)을 지저분하게 덕지덕지 발라야 합니다. 비즈니스 로직(1줄)보다 문서화 어노테이션(10줄)이 더 길어지는 현상이 발생해 코드가 더러워집니다.
// Swagger(Springdoc) 적용 예시: 코드가 매우 지저분해지는 단점
@RestController
@Tag(name = "회원 API", description = "회원 가입 및 조회를 담당합니다.")
public class UserController {
@Operation(summary = "회원가입", description = "신규 유저를 등록합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "400", description = "이메일 형식 오류")
})
@PostMapping("/users")
public ResponseEntity<User> signup(@RequestBody UserDto dto) {
return ResponseEntity.ok(userService.signup(dto));
}
}
2. Spring REST Docs: 코드 침투 제로, 테스트 강제화의 미학
Swagger의 '더러운 코드'를 혐오하는 우아한(?) 개발자 진영(예: 배달의민족, 토스 등 기술 블로그)에서 사랑하는 방식이 Spring REST Docs입니다.
장점:
- 프로덕션 코드(Controller)에 단 하나의 어노테이션도 추가되지 않습니다. 코드가 극도로 깨끗하게 유지됩니다.
- (핵심) 테스트 코드를 통과해야만 문서가 만들어집니다. 즉, 테스트 코드를 짜지 않거나 테스트가 실패하면(로직이 꼬이면) 아예 API 문서가 안 나옵니다. 따라서 프론트엔드는 "이 API 문서는 100% 작동이 보장되는 진실된 문서다"라고 확신할 수 있습니다.
단점:
- 적용하기가 끔찍하게 어렵고 진입 장벽이 높습니다.
- Swagger처럼 브라우저에서 버튼 눌러서 테스트해 보는 기능(Try it out)이 없습니다. 오직 정적인 HTML 문서만 예쁘게 보여줄 뿐입니다.
// REST Docs 적용 예시: 운영 코드가 아닌 '테스트 코드(MockMvc)' 파일에 문서화 로직을 적습니다.
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto)))
.andExpect(status().isOk())
.andDo(document("user-signup", // 테스트가 성공하면 user-signup.adoc 조각을 만들어라!
requestFields(
fieldWithPath("email").description("유저 이메일"),
fieldWithPath("password").description("비밀번호")
)));
3. 실무에서의 승자는? (하이브리드 전략)
대기업처럼 테스트 주도 개발(TDD) 문화가 강하게 잡혀있고 코드의 순수성을 중시한다면 REST Docs를 씁니다. 반면, 빠르게 기능을 치고 나가야 하는 스타트업이거나 프론트엔드와 핑퐁 테스트를 자주 해야 한다면 Swagger의 [Try it out] 기능을 포기할 수 없습니다.
최근에는 이 둘의 장점만 쏙 빼먹는 "REST Docs로 테스트 빡세게 해서 OpenAPI 스펙(JSON)을 추출한 뒤, 그걸 Swagger UI로 화려하게 그려주는 하이브리드(REST Docs + Swagger UI)" 구조도 인기입니다.
4. 마무리 및 주요 발전 방향
지금까지 API 개발을 마친 후 프론트엔드와의 협업에서 발생하는 끔찍한 커뮤니케이션 비용을 0으로 만들어주는 자동화 문서 도구, 편리한 API 문서화 도구인 Swagger(Springdoc)와 테스트 강제성을 부여하는 순수주의자 REST Docs의 장단점 및 실무 선택 가이드에 대해 다루어 보았습니다. 이제 거짓말 없는 API 스펙으로 평화로운 협업을 할 수 있게 되었습니다.
이제 모든 개발과 문서화가 끝나고 정말로 서버를 운영 중입니다. 그런데 치명적인 버그를 발견해서 소스코드를 수정하고 재배포(Deploy)를 해야 합니다. java -jar app.jar를 끄고 새 버전을 다시 켜는데 약 15초가 걸립니다. 이 15초 동안 사이트에 접속한 유저들은 "서버 연결할 수 없음(502 Bad Gateway)" 에러 창을 멍하니 쳐다보게 됩니다.
현대 인터넷 환경에서 1초의 다운타임(Downtime)은 곧 매출 하락입니다!
다음 설명에서는 "유저는 단 0.1초의 끊김도 느끼지 못한다!" 현대 인프라의 마법, Nginx(엔진엑스)를 활용한 블루/그린 무중단 배포(Zero-Downtime Deployment) 아키텍처에 대해 다루겠습니다.
'Framework > Spring Boot' 카테고리의 다른 글
| [Spring Boot] 파일 업로드와 AWS S3 클라우드 사용하기 (0) | 2026.10.13 |
|---|---|
| [Spring Boot] 트랜잭션 롤백과 전파 속성 (0) | 2026.10.13 |
| [Spring Boot] OAuth 2.0 소셜 로그인 구현(카카오 네이버 구글 로그인) (0) | 2026.10.12 |
| [Spring Boot] Spring Security - JSON Web Token 구현 (0) | 2026.10.12 |
| [Spring Boot] Redis 캐싱(Caching)을 이용하여 응답 속도 단축하기 (0) | 2026.10.08 |