프롤로그 - 슬랙에서 터진 폭탄
처음 백엔드를 혼자 만들었을 때의 일이다. 3주 동안 밤을 새워가며 REST API를 완성했다. 자랑스러운 마음으로 프론트엔드 개발자(동료)에게 슬랙 메시지를 보냈다.
"API 다 만들었어요! 테스트 서버:
https://api.example.com"
30분 후 슬랙 알림:
"어떻게 쓰는 건데요? 엔드포인트가 뭐고, 파라미터는 뭐예요?"
당황한 나는 급하게 엑셀 파일을 만들었다.
| 엔드포인트 | 메서드 | 파라미터 | 설명 |
|---|---|---|---|
/users | GET | - | 유저 목록 |
/users/{id} | GET | id (int) | 유저 상세 |
이 파일을 슬랙에 올렸다. "이거 보고 써주세요!"
2주 뒤, 또 슬랙 알림:
"문서에는
userId라고 되어 있는데 왜user_id에러가 나요?"
나는 일주일 전에 API 스펙을 수정했는데, 엑셀 문서는 업데이트를 깜빡했던 거였다. 프론트엔드 개발자는 2시간 동안 "왜 안 되지?" 하며 디버깅했다.
그날 나는 깨달았다. "엑셀 문서는 거짓말쟁이다."
왜 문서는 항상 낡는가?
백엔드 개발자의 일상은 이렇다:
- API 개발 (코드 작성)
- 문서 작성 (엑셀 / Notion / Confluence)
- 배포
- (일주일 후) 코드 수정
- 배포
- 문서 수정은... 깜빡함 ❌
왜 깜빡할까? 나는 이렇게 이해했다. 문서와 코드가 분리되어 있기 때문이다.
- 코드:
UserController.java - 문서:
API명세서_v2.xlsx(다른 폴더)
코드를 수정할 때 문서 파일이 눈에 안 보인다. 심지어 문서가 어디 있는지도 까먹는다.
더 큰 문제 - 버전 관리 지옥
내가 경험한 최악의 상황:
API명세서_최종.xlsx
API명세서_최종_진짜최종.xlsx
API명세서_최종_진짜최종_이번꺼.xlsx
API명세서_6월버전.xlsx
폴더에 이런 파일이 5개 있었다. 어떤 게 진짜 최신인지 아무도 모른다.
깨달음 - "코드가 곧 문서다"
이 문제를 해결하려면 문서와 코드를 한 곳에 두어야 한다. 그게 바로 Swagger (OpenAPI) 방식이었다.
기존 방식 (분리)
// UserController.java (코드)
@GetMapping("/users/{id}")
public User getUser(@PathVariable String id) {
return userService.findById(id);
}
// API명세서.xlsx (별도 파일)
엔드포인트: /users/{id}
메서드: GET
파라미터: id (String)
문제: 코드 수정 → 문서 수정 깜빡 → 거짓말 문서였다.
Swagger 방식 (통합)
// UserController.java (코드 + 문서)
@Operation(summary = "유저 조회", description = "ID로 유저 정보를 가져옵니다")
@ApiResponse(responseCode = "200", description = "성공")
@ApiResponse(responseCode = "404", description = "유저 없음")
@GetMapping("/users/{id}")
public User getUser(@PathVariable String id) {
return userService.findById(id);
}
어노테이션(@Operation)이 문서다.
코드를 수정하면 문서도 자동으로 바뀐다.
Swagger는 이 어노테이션을 읽어서 예쁜 웹사이트를 자동 생성한다:
https://api.example.com/swagger-ui
Swagger가 만들어주는 것
Swagger UI에 접속하면 다음을 볼 수 있다:
1. API 목록 (자동 생성)
GET /users/{id} - 유저 조회
POST /users - 유저 생성
DELETE /users/{id} - 유저 삭제
코드에서 @GetMapping, @PostMapping을 찾아서 자동으로 목록을 만들어준다.
2. 파라미터 설명
Path Parameters:
- id (String, required): 유저 ID
Response:
{
"id": "user123",
"name": "홍길동",
"email": "hong@example.com"
}
User 클래스의 필드를 읽어서 자동으로 예시 응답을 보여준다.
3. "Try it out" 버튼
이게 제일 강력했다. 웹 브라우저에서 바로 API를 실행할 수 있다.
id입력란에 "user123" 입력- "Execute" 버튼 클릭
- 실제 응답 확인:
{
"id": "user123",
"name": "홍길동",
"email": "hong@example.com"
}
포스트맨(Postman) 필요 없다.
내가 실제로 Swagger를 도입한 경험
내 블로그 백엔드(Next.js API Routes)에 Swagger를 붙였을 때:
Before (Swagger 없음)
프론트엔드 개발자(내 동료):
"POST /posts 엔드포인트 body 형식이 뭐예요?"
나 (백엔드):
"잠깐만요... (코드 뒤적뒤적)
title,content,tags필드 보내주세요."
30분 후:
"tags가 string이에요 array예요?"
나:
"아... array입니다.
["tag1", "tag2"]이렇게요."
이런 대화가 하루에 5번이었다.
After (Swagger 도입)
/**
* @swagger
* /api/posts:
* post:
* summary: 블로그 글 생성
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* title:
* type: string
* content:
* type: string
* tags:
* type: array
* items:
* type: string
*/
export default function handler(req, res) { ... }
프론트엔드 개발자:
"Swagger 문서 보고 작업했어요. 바로 됐습니다."
질문 0개였다.
Swagger 도입 전 vs 후 비교
| 항목 | Swagger 없음 | Swagger 있음 |
|---|---|---|
| 문서 작성 시간 | 1시간 (엑셀 정리) | 10분 (어노테이션) |
| 문서 업데이트 | 수동 (자주 깜빡) | 자동 (코드와 동기화) |
| 테스트 방법 | Postman 켜기 | 웹 브라우저에서 바로 |
| 프론트-백 커뮤니케이션 | 슬랙 질문 폭탄 | 문서 보고 끝 |
| 신규 개발자 온보딩 | 1주일 (API 설명) | 1일 (Swagger 링크) |
실제 코드: Spring Boot + Swagger
실제 내가 회사에서 쓴 설정이다.
1. 의존성 추가 (build.gradle)
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
2. 컨트롤러에 어노테이션
@RestController
@RequestMapping("/api/users")
@Tag(name = "User API", description = "유저 관리 API")
public class UserController {
@Operation(
summary = "유저 목록 조회",
description = "모든 유저의 목록을 페이지네이션으로 반환합니다"
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@GetMapping
public List<User> getUsers(
@Parameter(description = "페이지 번호 (0부터 시작)")
@RequestParam(defaultValue = "0") int page
) {
return userService.findAll(page);
}
}
3. 실행 후 접속
http://localhost:8080/swagger-ui/index.html
끝이다. 문서가 자동으로 생성된다.
Swagger를 더 효과적으로 쓰는 팁
Tip 1: DTO에 예시 값 넣기
@Schema(description = "유저 생성 요청")
public class CreateUserRequest {
@Schema(description = "유저 이름", example = "홍길동")
private String name;
@Schema(description = "이메일", example = "hong@example.com")
private String email;
}
Swagger UI에서 "Try it out"을 누르면 예시 값이 자동으로 채워진다.
Tip 2: 인증 헤더 설정
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.components(new Components()
.addSecuritySchemes("bearerAuth",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
)
);
}
}
이제 Swagger UI 상단에 "Authorize" 버튼이 생긴다. JWT 토큰을 한 번 입력하면 모든 API 테스트에 자동으로 헤더가 붙는다.
Tip 3: 환경별 Swagger 비활성화
운영 환경(Production)에서는 Swagger를 끄고 싶을 때:
# application-prod.yml
springdoc:
swagger-ui:
enabled: false
보안상 API 스펙을 외부에 공개하고 싶지 않을 때 사용한다.
Swagger의 한계와 대안
한계 1 - 어노테이션 지옥
API가 복잡하면 어노테이션이 코드보다 길어진다.
@Operation(...)
@ApiResponse(...)
@ApiResponse(...)
@Parameter(...)
@Parameter(...)
@GetMapping("/users")
public List<User> getUsers(...) {
// 실제 로직 3줄
}
해결책: 별도 OpenAPI YAML 파일 작성 (Code-First가 아닌 Contract-First).
한계 2 - 런타임에만 확인 가능
Swagger는 서버를 띄워야 문서를 볼 수 있다. 컴파일 타임에는 확인 불가였다.
해결책: Postman Collection / Insomnia 사용 (파일로 관리 가능).
마치며 - "엑셀은 이제 그만"
처음 내가 엑셀로 API 문서를 만들었을 때는 "이게 최선"이라고 생각했다. 하지만 코드와 문서가 따로 놀면 문서는 항상 거짓말을 한다는 걸 배웠다.
Swagger를 도입한 후:
- 문서 업데이트를 깜빡할 일이 없어졌다.
- 프론트엔드 개발자와의 질문/답변이 90% 줄었다.
- 신규 개발자 온보딩 시간이 1주일 → 1일로 단축됐다.
아직도 엑셀/워드로 API 명세서를 작성하고 계신가요? 오늘 당장 Swagger를 도입하세요. 평화가 찾아옵니다.
11. 비동기 API와 GraphQL 문서화 한 걸음 더
REST API만 문서화하면 좋겠지만, 세상은 더 복잡하다.
1) 비동기 API (Webhook / WebSocket) 문서화
"결제 완료되면 제가 웹훅(Webhook) 쏴드릴게요"라고 말만 하면 안 된다. 받는 사람 입장에서 어떤 JSON이 날아올지 모르면 서버를 못 만든다.
- AsyncAPI: REST에 Swagger가 있다면, 이벤트 기반 아키텍처엔 AsyncAPI 스펙이 있다. 카프카(Kafka)나 웹소켓 메시지 형식을 정의할 때 쓴다.
2) GraphQL 문서화
GraphQL은 스키마(Schema) 자체가 강력한 문서다. 하지만 "어떤 필드를 써야 하는지" 설명이 부족하다.
- GraphiQL: 브라우저에서 스키마를 탐색하는 도구다. 여기에 주석(Description)을 꼼꼼히 달아둬야 한다.
- Introspection: 클라이언트가 "너 무슨 타입 있어?"라고 물어볼 수 있는 기능이다. 보안상 프로덕션에서는 끄는 게 좋지만, 개발 단계에서는 최고의 문서 도구였다.
3) gRPC 문서화 (Protobuf)
.proto 파일 자체가 IDL(Interface Definition Language)이라 문서 역할을 하지만, 이걸 읽을 줄 아는 사람은 드물다.
protoc-gen-doc플러그인을 쓰면.proto파일을 예쁜 HTML이나 마크다운으로 변환해준다.
8. 논쟁: Swagger vs Spring REST Docs
백엔드 개발자 사이의 영원한 난제다. 실무에서도 자주 논의되는 주제다.
| 특징 | Swagger (OpenAPI) | Spring REST Docs |
|---|---|---|
| 작성 위치 | 프로덕션 코드 (Controller) | 테스트 코드 (Test Code) |
| 장점 | 적용이 쉽고, UI가 화려하며 테스트 가능 | 제품 코드에 영향 없음(Zero Pollution). 테스트 통과해야 문서 나옴(신뢰성 100%). |
| 단점 | 어노테이션 도배로 코드가 지저분해짐. | 작성이 어렵고 귀찮음(Asciidoc 문법). |
| 추천 | 스타트업, 초기 개발, 내부 API | 대규모 시스템, 외부 공개용 API, 품질 중요 |
내 결론: 초기에는 Swagger로 빠르게 치고 나가고, 시스템이 안정화되고 외부 공개가 필요해지면 REST Docs로 전환하거나 병행하는 전략이 좋다고 생각한다. 최근에는 Swagger UI + REST Docs를 합쳐서 쓰는 라이브러리(com.epages.restdocs-api-spec)도 유행이다.
9. API 문서화의 3대 원칙 (Best Practices)
문서가 "예쁘게" 나오는 것보다 중요한 원칙들을 정리해본다.
1) 버전 관리 (Versioning)
API는 살아있는 생물이라 계속 바뀐다.
- URI Versioning:
/api/v1/users(가장 직관적, 추천) - Header Versioning:
Accept: application/vnd.company.v1+json(깔끔하지만 테스트하기 귀찮다) - Breaking Change 금지: v1을 v2로 바꿀 때는 반드시 v1을 일정 기간(Deprecation Period) 유지해줘야 한다. 말 없이 바꾸면 클라이언트 개발자가 당신을 찾아갈 것이다.
2) 실패 케이스(Error Response) 명시
"성공하면 뭐 주나요?"는 다들 잘 쓴다. 하지만 프론트엔드 개발자가 진짜 궁금한 건 "망했을 때 뭐 주나요?"였다.
- 400 (Bad Request): 필수 파라미터 누락일 때 어떤 에러 코드?
- 401 (Unauthorized): 토큰 만료 시 메시지는?
- 403 (Forbidden): 권한 없음. 이걸 상세하게 적어주는 사람이 진짜 "센스 있는" 백엔드 개발자다.
3) 일관된 응답 포맷 (Envelope Pattern)
모든 API의 응답 껍데기(Envelope)를 통일하자.
{
"code": "SUCCESS",
"message": "요청 성공",
"data": { ...실제 데이터... }
}
어떤 API는 배열을 바로 주고([]), 어떤 건 객체({})를 주면 프론트엔드에서 공통 처리를 못 한다.
11. 트렌드 - Docs as Code (문서를 코드처럼)
최근 테크 기업들은 "문서도 코드다"라는 철학을 실천하고 있다. 단순히 Swagger를 쓰는 것을 넘어서, 전체 기술 문서를 GitHub에서 관리하는 것이다.
왜 이렇게 할까?
- 버전 관리: Git을 쓰면 누가 언제 문서를 고쳤는지 히스토리가 다 남는다.
git blame으로 범인을 찾을 수 있다. - Pull Request 리뷰: 문서를 수정할 때도 동료의 리뷰(Approve)를 받아야 병합된다. 오타나 잘못된 정보를 미리 거를 수 있다.
- CI/CD 자동 배포: 문서를 커밋(
push)하면 Vercel이나 Netlify가 알아서 정적 사이트로 빌드해서 배포해준다.
추천 도구
- Docusaurus: 페이스북(Meta)에서 만든 문서 도구. 리액트 기반이라 커스텀이 쉽고 블로그 기능도 있다. (지금 보시는 이 블로그도 MDX 기반이다!)
- GitBook: 깔끔한 UI가 장점이지만, 유료화 정책이 좀 있다.
- Hugo / Jekyll: 고전적인 정적 사이트 생성기.
결국 "개발자가 가장 익숙한 도구(IDE, Git, Markdown)로 문서를 쓰게 하자"가 핵심이다.
워드나 컨플루언스(Confluence)는 개발자에게 너무 무겁고, 흐름을 끊는다. 게다가 마크다운으로 작성하면 코드 하이라이팅이 예쁘게 들어가서 읽기도 좋다. 나는 예쁜 코드를 보면 기분이 좋아지고, 기분이 좋아지면 문서를 더 잘 쓰게 되는 선순환을 경험했다. (진짜다.)
12. 핵심 정리
Q1. Code-First와 Design-First의 차이는?
- 답변:
- Code-First: 코드를 먼저 짜고 문서를 자동 생성(Swagger). 빠르지만 문서 품질이 낮을 수 있음.
- Design-First: YAML(OpenAPI Spec)을 먼저 작성하고 코드를 생성. 프론트/백엔드가 동시에 개발 시작 가능하며 인터페이스 설계가 더 정교함.
Q2. HATEOAS가 뭔가요? 꼭 써야 하나요?
- 답변: REST API의 성숙도 모델 중 최상위 단계입니다. 응답에 "다음 행동을 할 수 있는 링크(Link)"를 포함하는 것입니다. (예: 페이징 링크). 하지만 구현 난이도 대비 실효성이 낮아 실제로는 잘 안 쓰는 추세입니다.
Q3. API 문서를 엑셀로 관리하면 안 되나요?
- 답변: 초기엔 편할지 몰라도, 동기화(Synchronization) 문제가 필연적으로 발생한다. 코드는 바뀌었는데 문서는 그대로인 상황이 반복되면 문서의 신뢰도가 0이 된다. "코드가 곧 문서"가 되는 자동화 도구(Swagger/REST Docs)를 써야 한다.