Devin.KR

API 명세와 클라이언트 관찰

65분 안팎

학습 목표

요청 예시·빈 목록·실패 응답을 API 명세에 작성합니다.

개념

동료가 구현을 몰라도 호출할 수 있게 씁니다

API 명세의 독자는 BookRepository 코드를 읽지 않은 화면 개발자나 다음 모듈 작성자입니다. 요청을 보내면 어떤 상태·헤더·본문을 받는지 알 수 있어야 합니다. 구현 클래스 목록만 적으면 빈 목록을 오류로 표시하거나 생성 뒤 다른 ID를 조회하는 실수가 생깁니다. 이번 레슨은 새 코드 대신 지금 동작하는 도서 API의 계약과 재현 방법을 작성합니다. 제출물은 API-SPEC.md와 성공·경계·실패를 비교한 관찰 기록입니다.

메서드와 경로를 한 행으로 묶습니다

GET /books와 POST /books는 같은 경로라도 다른 행위입니다. 명세 표에는 메서드·경로, 요청 헤더와 본문, 성공 상태·헤더·본문, 경계·실패 사례를 각각 씁니다. GET /books/{id}의 id는 정수로 변환하며, 저장된 도서의 키는 양수입니다. 0이나 음수도 정수 변환은 성공하지만 저장된 키가 없으므로 404입니다. 숫자로 변환하지 못하는 경로와 변환했지만 존재하지 않는 키를 나누면 400과 404를 혼동하지 않습니다. 추후 수정·삭제·인증을 추가할 때 현재 지원 범위와 섞지 않습니다.

기본 URL은 실습용 127.0.0.1:18083이고 경로는 /books입니다. 운영 호스트나 비밀값을 예시에 넣지 않습니다. 목록 순서는 등록 순서이며 정렬·검색·페이지 기능은 아직 없습니다. 같은 제목의 다른 도서를 허용하고 title의 양끝 공백을 보존합니다. 클라이언트가 목록의 첫 번째 위치를 상세 ID로 계산하지 않고 응답의 id를 사용하도록 설명합니다.

예시는 전제 조건을 포함합니다

빈 목록 예시는 독립된 비어 있는 파일을 사용하는 전제에서 200과 []입니다. 도서 한 권이 이미 있는 파일에 같은 명령을 보내면 배열에 값이 나오는 것이 정상입니다. 생성 예시에서 ID 1은 빈 파일의 첫 성공 요청이라는 전제에서만 맞습니다. 기존 파일에 ID 10이 있다면 새 ID는 11일 수 있습니다. 응답의 Location을 사용해 상세 조회를 이어가는 절차를 적으면 환경이 달라도 같은 계약을 확인할 수 있습니다.

요청 JSON은 {"title":"HTTP 노트"}이며 요청 Content-Type은 application/json입니다. 성공 본문은 id, title, borrowed를 포함하는 객체이고 새 도서의 borrowed는 false입니다. 빈 JSON 객체, 공백 제목, JSON 문법 오류는 각각 400의 다른 입력 원인입니다. 탭·CR·LF는 파일 형식 제한 때문에 거부한다는 이유도 적습니다. 명세에는 실제로 구현한 검증만 포함하고 제목 길이 제한처럼 없는 정책을 추가하지 않습니다.

헤더와 오류 계약을 명시합니다

성공한 POST의 상태는 201이고 Location은 새 상세 자원의 상대 URI입니다. 200과 같은 뜻이라고 적지 않습니다. 응답 Content-Type은 JSON이고 요청 Content-Type과 다른 메시지의 속성입니다. Date나 서버 구현 이름처럼 실행 환경에 따라 달라지는 헤더는 공개 계약 필드가 아닙니다. 전체 원시 응답과 비교 대상 필드를 분리하면 날짜가 달라졌다는 이유로 정상 호출을 실패로 판정하지 않습니다.

현재 오류 응답은 상태 코드를 계약으로 삼고 본문 code·message의 고정 스키마는 약속하지 않습니다. Python 모의 서버의 NOT_FOUND와 Java MockMvc의 응답을 같은 형식이라고 쓰지 않습니다. 404는 없는 상세, 415는 지원하지 않는 요청 본문 형식, 405는 지원하지 않는 메서드, 409는 이 구현에서 ID 공간 고갈일 때입니다. 데이터베이스 제약 충돌이나 동시 대여 충돌은 아직 구현하지 않았으므로 409의 다른 의미를 지금 명세에 섞지 않습니다.

클라이언트는 상태와 업무 결과를 함께 봅니다

curl -sS -i는 헤더와 본문을 관찰하는 재현 명령입니다. JSON POST에는 -H와 --data를 사용합니다. 오류 응답을 비교할 때는 -f로 곧바로 실행을 중단할지, 응답을 수신해 상태로 분기할지를 정해야 합니다. 이 실습은 실패 응답을 읽는 목적이므로 상태를 직접 비교합니다. 요청 URL을 따옴표로 묶고 실제 응답 Location을 기반으로 상세 조회를 실행하도록 예시와 설명을 맞춥니다.

POST 응답이 사라졌다고 같은 요청을 자동으로 반복하면 두 권이 만들어질 수 있습니다. 지금은 멱등 키가 없으므로 재시도를 안전하게 보장한다고 쓰지 않습니다. GET은 도서를 생성하지 않고 여러 번 조회할 수 있습니다. 이 차이는 화면의 등록 버튼 중복 클릭과 네트워크 재시도에 영향을 줍니다. 명세에는 인증·인가가 아직 없다는 구현 범위도 적고 뒤 권한 모듈에서 계약이 확장될 것을 구분합니다.

재현 기록에는 상태를 바꾸는 순서를 적습니다

목록 조회→생성→Location 상세 조회→없는 ID 조회 순서를 한 시나리오로 기록합니다. 첫 목록이 비어 있어야 한다면 새 임시 폴더 아래의 새 파일로 시작합니다. 등록한 도서를 운영 데이터처럼 보관할 필요가 없으므로 실습 데이터 위치를 명시합니다. 같은 시나리오를 다시 실행할 때 새 파일인지 기존 파일인지 기록하지 않으면 ID와 목록 수가 달라진 이유를 해석할 수 없습니다.

영속성 검사는 성공한 POST 뒤 같은 catalog.path를 새 인스턴스로 읽는 것입니다. 테스트는 reloadInNewContext에서 별도의 컨텍스트와 파일 저장소를 생성합니다. CLI 프로세스와 앱을 동시에 실행해서 같은 파일을 수정하는 실험은 보장 범위를 벗어납니다. 문서에는 단일 작성자, 사전 준비한 부모 폴더, 같은 폴더 원자 이동 지원, 별도 전원 장애 내구성 보장 없음이라는 이전 모듈의 제한을 유지합니다.

자동 증거와 수동 증거의 역할

./mvnw test는 누적 Java 계약을 검증하고 report.py는 Surefire 결과를 요약합니다. MockMvc는 메서드·경로·상태·JSON 변환·Location을 확인하지만 실제 DNS·TCP·TLS·프록시를 통과한 요청을 검증하지 않습니다. BootContextTest도 웹 서버 없이 빈 탐색을 확인합니다. 보고서가 통과했다고 도메인의 인증서나 방화벽까지 정상이라고 적지 않습니다. 로컬 curl 실습의 실행 가능 여부와 결과는 별도 기록합니다.

명세 검토에서는 각 행의 근거를 테스트와 관찰 기록에 연결합니다. 예를 들어 생성 행은 createAndFollowLocation으로 201·Location·후속 상세 조회를 확인하고, 영속성 행은 reloadInNewContext로 새 저장소에서 읽은 제목을 확인합니다. 실제 curl 기록이 없다면 네트워크 관찰 항목을 미확인으로 남깁니다. 테스트 이름을 적는 목적은 검사 개수를 늘리는 것이 아니라 명세 변경 시 어떤 근거를 다시 확인해야 하는지 동료가 찾게 하는 것입니다.

명세와 구현이 어긋날 때 읽는 순서

명세는 201인데 관찰은 200이면 메서드와 URL, 선택한 starter 또는 solution, 컨트롤러의 ResponseEntity를 확인합니다. Location의 숫자가 예시와 다르면 먼저 초기 파일 상태와 기존 최대 ID를 봅니다. title이 다른 필드에 담기면 DTO와 JSON 경로를 비교합니다. 명세를 관찰값에 맞춰 무작정 바꾸지 않고 요구 계약, 구현, 테스트 중 어느 쪽이 잘못되었는지 판단합니다.

HTTP 상태를 받지 못한 연결 실패는 404와 다릅니다. 서버 시작 로그만 있고 curl이 연결하지 못하면 포트와 바인딩 주소, 실행 환경을 확인합니다. 여기서 확인할 수 없는 네트워크 원인은 추정이라고 기록합니다. 요청 시간의 측정 경계와 지연 분석은 더 읽기의 요청 추적 장에서 확장하며 이번 명세에 실행하지 않은 수치나 성능 보장을 넣지 않습니다.

제출물의 검토 기준

API-SPEC.md에는 네 정상·경계 사례의 재현 명령과 요청·상태·응답을 적고 입력 오류·미지원 메서드·미지원 형식의 상태를 덧붙입니다. 테스트 이름을 근거에 연결하고 새 파일에서 시작한다는 전제를 밝힙니다. 동료가 문서만 보고 빈 목록 조회, 새 도서 추가, 응답 Location 조회, 없는 ID 판정을 재현할 수 있어야 합니다. 자체 검토에서는 임의 날짜나 고정 ID를 계약으로 잘못 적지 않았는지 확인합니다.

미션의 최종 설명에는 앞 모듈 저장소와 50개 테스트를 유지했다는 사실, 컨트롤러·서비스·구성의 책임, 새 컨텍스트 복원의 근거를 씁니다. API 명세를 코드와 함께 고치는 습관은 다음 SQL 저장소 교체에서 외부 계약을 지키는 기준이 됩니다. DB가 바뀌어도 클라이언트가 조회와 생성 방식까지 다시 알아야 하는 상황을 피하려면 지금 약속한 경계를 분명하게 남겨야 합니다.

따라하기

명세의 표를 구성합니다

미션 starter의 API-SPEC.md를 작성합니다. GET 목록·GET 상세·POST 생성 각각 요청과 성공·경계·실패를 표의 행으로 분리합니다. 본문의 제출 기준을 사용합니다.

응답에서 다음 요청을 정합니다

아래는 본인 컴퓨터에서 README대로 서버를 실행한 뒤 별도 터미널에서 관찰할 명령입니다. 먼저 빈 파일인지 확인하고 응답 Location을 읽어 상세 경로를 결정합니다. 서버를 띄운 터미널에서 관찰이 끝나면 Ctrl-C로 종료합니다. 이 명령은 작성 샌드박스에서 실행하지 못해 output을 비웠습니다.

curl -sS -i http://127.0.0.1:18083/books
curl -sS -i -H 'Content-Type: application/json' --data '{"title":"HTTP 노트"}' http://127.0.0.1:18083/books

실패 재현을 문서화합니다

실제 Location 조회와 별도로 없는 ID를 조회합니다. curl 종료 상태와 HTTP 상태를 구분해 기록하고 Java 오류 본문을 fixture와 같다고 적지 않습니다. 미실행 결과는 비워 둡니다.

curl -sS -i http://127.0.0.1:18083/books/99

자동 증거를 연결합니다

먼저 ./mvnw test를 실행하고 성공한 뒤 아래 집계 명령을 실행합니다. output은 집계 명령의 실제 출력입니다.

완성한 미션에서 테스트를 실행하고 명세 각 행에 연결되는 테스트 이름을 적습니다. 아래는 solution의 실제 보고서 집계이며 네트워크 테스트 결과가 아닙니다.

python3 report.py

실행 결과

Tests=64
Failures=0
Errors=0
Skipped=0

동료 관점으로 최종 검토합니다

문서만 읽어 빈 목록·추가·Location 상세·없는 상세를 재현할 수 있는지 확인합니다. 초기 파일 조건·재시도 제한·단일 작성자·오류 DTO 후속 범위가 빠지지 않아야 합니다.

확인 문제

실습

미션의 API-SPEC.md를 제출합니다. 메서드·경로·요청 Content-Type·JSON·성공 상태·Location·응답 DTO를 기록합니다. 빈 목록·생성·상세·없는 상세의 재현 명령과 초기 파일 조건, 400·405·415·409 의미, 테스트 이름, 재시도 제한·단일 작성자 범위를 포함합니다. 오류 본문은 후속 계약이라고 구분하고 미실행 출력은 비워 둡니다.

더 읽기

면접 질문

  • API 명세에 초기 데이터 상태와 재현 순서를 적는 이유는 무엇인가요?
  • MockMvc 통과 결과를 API 명세의 증거로 사용할 때 검증 범위를 어떻게 설명하나요?