지난 글에서는 HTTP가 무엇인지와 HTTP 메시지의 기본 구조에 대해 공부했다.
이번에는 조금 더 실제 백엔드 개발과 가까운 내용이다.
API의 URL은 어떻게 설계해야 할까?
회사에 입사해서 회원 관리 API를 만들어야 한다고 가정해보자.
다음과 같은 요구사항이 주어졌다.
회원 목록 조회
회원 조회
회원 등록
회원 수정
회원 삭제
처음 API를 설계한다면 이런 식으로 만들고 싶을 수도 있다.
/read-member-list
/read-member
/create-member
/update-member
/delete-member
딱 봐도 무엇을 하는 API인지 알 수 있다.
그런데 정말 이게 좋은 URI 설계일까?
이번 수업에서 가장 중요하게 들었던 것은 이것이었다.
API URI 설계에서 가장 중요한 것은 리소스를 식별하는 것이다.
처음에는 이 말이 조금 추상적으로 느껴졌다.
그래서 이번 글에서는
리소스가 무엇인지
URI에는 무엇을 넣어야 하는지
조회 / 등록 / 수정 / 삭제는 어디에 표현하는지
GET / POST / PUT / PATCH / DELETE는 각각 어떤 의미인지
안전 / 멱등 / 캐시 가능은 무엇인지
를 하나씩 연결해서 정리해보려고 한다.
회원 관리 시스템을 다시 생각해보자.
회원 목록 조회
회원 조회
회원 등록
회원 수정
회원 삭제
여기서 리소스는 무엇일까?
처음에는
회원 조회
회원 등록
회원 수정
같은 기능 자체가 리소스라고 생각하기 쉽다.
그런데 그렇지 않다.
여기서 리소스는 회원(Member) 그 자체다.
수업에서 재미있는 비유가 하나 나왔다.
스타크래프트에서
"미네랄을 캐라."
라는 명령이 있다고 해보자.
이때
캐라
가 리소스일까?
아니다.
미네랄
이 리소스다.
캐라는 미네랄이라는 리소스를 대상으로 수행하는 행위다.
회원 관리도 똑같이 생각할 수 있다.
회원
→ Resource
조회
등록
수정
삭제
→ Resource에 대한 행위
여기서 API URI 설계의 중요한 원칙이 나온다.
URI에는 행위보다 리소스를 표현한다.
처음의 요구사항을 다시 보자.
회원 목록 조회
회원 조회
회원 등록
회원 수정
회원 삭제
여기서 행위를 빼버린다.
회원 목록
회원
회원
회원
회원
결국 남는 것은
회원이라는 리소스
다.
그러면 회원 리소스를 URI에 매핑할 수 있다.
일반적으로 컬렉션을 표현할 때는 복수형 명사를 사용하는 것을 권장한다.
따라서
/member
보다는
/members
처럼 표현할 수 있다.
회원 한 명을 식별하고 싶다면
/members/100
과 같이 표현한다.
정리하면 다음과 같다.
/members
→ 회원 컬렉션
/members/100
→ ID가 100인 회원
여기까지만 보면 꽤 깔끔하다.
그런데 문제가 하나 생긴다.
회원 목록도
/members
이고,
회원 등록도
/members
라고 하면 둘을 어떻게 구분할까?
처음에는 URI 뒤에 다시 동사를 붙이면 되지 않을까 생각할 수 있다.
/members/get
/members/create
/members/delete
그런데 그렇게 하면 다시 URI가 행위를 표현하게 된다.
여기서 HTTP가 가진 중요한 기능이 등장한다.
행위는 HTTP Method가 표현한다.
즉,
URI
→ Resource 식별
HTTP Method
→ Resource에 수행할 행위
로 역할을 나눈다.
그래서 회원 관리 API는 다음처럼 설계할 수 있다.
GET /members
→ 회원 목록 조회
POST /members
→ 회원 등록
GET /members/100
→ 100번 회원 조회
PATCH /members/100
→ 100번 회원 일부 수정
DELETE /members/100
→ 100번 회원 삭제
이제 같은 URI라도 HTTP Method에 따라 수행할 작업을 구분할 수 있다.
개인적으로 여기서 URI 설계가 조금 더 명확하게 이해됐다.
처음에는
URL만 봐도 무슨 동작인지 전부 표현해야 하는 것 아닌가?
라고 생각했다.
그런데 실제로는
URI
+
HTTP Method
를 함께 봐야 하나의 요청이 완성되는 것이다.
HTTP에는 여러 Method가 존재한다.
대표적인 Method는 다음과 같다.
GET
POST
PUT
PATCH
DELETE
이외에도
HEAD
OPTIONS
CONNECT
TRACE
등이 있다.
이번에는 웹 개발에서 자주 사용하는 주요 Method부터 살펴보자.
GET은 리소스를 조회할 때 사용한다.
예를 들어 100번 회원을 조회한다고 해보자.
GET /members/100 HTTP/1.1
Host: example.com
이를 말로 풀면
"서버야, 100번 회원 리소스를 줘."
정도가 된다.
서버는 요청을 확인하고 해당 회원 정보를 찾는다.
그리고 JSON 형태의 데이터를 응답할 수 있다.
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 100,
"username": "daehyun",
"age": 27
}
전체 흐름을 단순하게 나타내면 다음과 같다.
Client
│
│ GET /members/100
▼
Server
│
│ 100번 회원 조회
▼
JSON 생성
│
│ 200 OK
▼
Client
조회할 때도 서버에게 추가적인 조건을 전달해야 하는 경우가 있다.
예를 들어 Google 검색을 생각해보자.
https://www.google.com/search?q=hello&hl=ko
여기서
q=hello
hl=ko
가 Query Parameter다.
HTTP 요청은 다음과 비슷한 형태가 된다.
GET /search?q=hello&hl=ko HTTP/1.1
Host: www.google.com
검색이나 목록 조회에서는 Query Parameter를 많이 사용한다.
예를 들어
GET /members?age=20
이라면
20살인 회원을 조회해줘.
와 같은 의미로 사용할 수 있다.
또
GET /products?category=computer&sort=price
라면
category=computer
→ 컴퓨터 카테고리
sort=price
→ 가격순 정렬
처럼 조회 조건을 전달할 수 있다.
따라서 GET은 보통
리소스 조회
+
Query Parameter를 통한 조회 조건 전달
에 사용한다.
GET도 기술적으로 Message Body를 포함할 수 있는 경우가 있다.
하지만 실무에서는 거의 사용하지 않는 편이다.
서버나 중간 장비가 GET Body를 지원하지 않는 경우도 있고 상호운용성이 좋지 않기 때문이다.
그래서 일반적으로 GET에서 데이터를 전달할 때는
Query Parameter
를 사용한다고 이해하는 것이 좋다.
다음은 POST다.
POST는 클라이언트가 서버에게 데이터를 전달하면서
"이 데이터를 받아서 네가 정의한 방식대로 처리해줘."
라고 요청하는 Method라고 생각하면 이해하기 쉽다.
대표적인 사용 사례가 신규 리소스 등록이다.
회원 등록을 생각해보자.
POST /members HTTP/1.1
Content-Type: application/json
{
"username": "daehyun",
"age": 27
}
여기서 URI는
/members
이다.
즉 회원 컬렉션에 요청을 보낸다.
서버는 요청을 받아 새로운 회원을 생성할 수 있다.
POST /members
{
username: daehyun,
age: 27
}
↓
Server
↓
새로운 회원 생성
↓
ID = 100
그리고 다음과 같이 응답할 수 있다.
HTTP/1.1 201 Created
Location: /members/100
여기서 중요한 부분이 있다.
클라이언트는 새로운 회원이
/members/100
이 될지
/members/101
이 될지 모른다.
새로운 리소스의 URI를 서버가 결정한다.
여기서 처음에 조금 헷갈렸다.
POST를
POST = Create
라고 외우면 간단해 보인다.
하지만 POST의 역할은 그것보다 넓다.
POST는 요청 데이터를 서버가 정의한 방식으로 처리하게 할 수 있다.
대표적으로
HTML Form 회원가입
상품 주문
게시글 작성
댓글 등록
결제 요청
프로세스 실행
등에 사용할 수 있다.
즉 신규 리소스 생성뿐만 아니라 단순한 CRUD로 표현하기 어려운 프로세스 처리에도 사용할 수 있다.
예를 들어
POST /orders/100/cancel
처럼 특정 주문을 취소하는 프로세스를 실행하는 식으로 사용할 수도 있다.
이런 URI를 Controller URI 또는 Control URI 형태로 볼 수 있다.
REST스럽게 리소스와 Method만으로 표현하기 어려운 동작에서 사용할 수 있다.
처음 메모에는
POST는 무조건 데이터를 Body에 담아서 보내야 한다.
라고 적어두었다.
그런데 조금 더 정확하게 말하면 POST라고 해서 반드시 Message Body가 존재해야 하는 것은 아니다.
POST의 핵심은
대상 리소스에 요청 데이터를 전달하여 리소스가 정의한 방식으로 처리하도록 요청하는 것
이다.
실제 웹 API에서는 JSON 등의 Body를 전달하는 경우가 많기 때문에
POST
→ Message Body
를 자주 보게 되는 것이다.
POST는 활용 범위가 꽤 넓다.
그래서 다음 정도로 기억하면 좋을 것 같다.
POST는 서버에게 요청 데이터를 전달하고, 대상 리소스가 정의한 방식대로 처리하도록 요청하는 Method다.
대표적으로
새로운 리소스 생성
프로세스 실행
다른 HTTP Method로 표현하기 애매한 처리
등에 사용할 수 있다.
PUT은 처음 배울 때 꽤 헷갈렸다.
보통
PUT = 수정
이라고 외우기 쉽기 때문이다.
그런데 PUT의 핵심은 단순 수정이 아니다.
대상 리소스를 요청 데이터로 완전히 대체한다.
파일을 폴더에 복사하는 상황을 생각하면 이해하기 쉽다.
기존 파일 존재
+
같은 이름의 새 파일 복사
↓
기존 파일을 새 파일로 덮어씀
PUT도 비슷하다.
예를 들어
PUT /members/100
으로 다음 데이터를 보냈다고 해보자.
{
"username": "daehyun"
}
기존 100번 회원 데이터가
{
"username": "daehyun",
"age": 27
}
이었다면 PUT의 의미는
age만 그대로 두고
username만 수정
이 아니다.
요청으로 전달된 표현으로 리소스 전체를 대체하는 것에 가깝다.
따라서 결과가
{
"username": "daehyun"
}
처럼 될 수 있다.
그래서 PUT을 사용할 때는 주의해야 한다.
POST와 PUT의 중요한 차이가 하나 있다.
누가 리소스의 URI를 결정하는가?
POST 기반 등록에서는
POST /members
처럼 서버가 새로운 리소스의 URI를 결정했다.
반면 PUT에서는 클라이언트가 직접 URI를 알고 지정한다.
PUT /files/star.jpg
클라이언트가
"나는
/files/star.jpg라는 리소스를 만들거나 대체할 거야."
라고 명확하게 지정한다.
비교하면 다음과 같다.
POST
POST /members
↓
서버가 ID 결정
↓
/members/100
반면
PUT
PUT /files/star.jpg
↓
클라이언트가 URI 직접 지정
이다.
PUT은 지정한 위치에 리소스가 없으면 새롭게 생성할 수도 있다.
PUT /files/star.jpg
요청을 보냈는데
/files/star.jpg
가 존재하지 않는다면 새로운 리소스를 생성할 수 있다.
따라서 PUT은
리소스가 있으면
→ 완전히 대체
리소스가 없으면
→ 생성
이라고 기억할 수 있다.
여기까지 보고 나니 처음 생각했던
PUT = 수정
이라는 설명이 조금 애매하다는 것을 알게 됐다.
PUT은 더 정확하게
리소스 전체 대체
에 가깝다.
그러면 이런 생각이 든다.
"나는 회원의 이름만 바꾸고 싶은데?"
이때 사용하는 것이 PATCH다.
PATCH는 대상 리소스의 일부를 변경할 때 사용한다.
예를 들어 기존 데이터가 다음과 같다고 해보자.
{
"username": "daehyun",
"age": 27
}
이름만 수정하고 싶다면
PATCH /members/100
과 함께
{
"username": "newName"
}
을 전달할 수 있다.
그러면
username
→ 변경
age
→ 유지
처럼 필요한 부분만 수정할 수 있다.
그래서 다음처럼 구분하면 기억하기 쉽다.
PUT
→ 전체 대체
PATCH
→ 부분 변경
만약 서버나 클라이언트 환경에서 PATCH를 사용하기 어려운 경우에는 POST를 이용해 변경 기능을 구현하는 경우도 있다.
DELETE는 이름 그대로 리소스를 삭제한다.
예를 들어
DELETE /members/100
은
100번 회원 리소스를 삭제해줘.
라는 요청이다.
Client
│
│ DELETE /members/100
▼
Server
│
│ 100번 회원 삭제
▼
삭제 완료
다른 Method에 비해 의미가 상당히 직관적이다.
HEAD는 GET과 비슷하다.
하지만 서버는 응답에서 Message Body를 제외하고 Status Line과 Header만 반환한다.
예를 들어 리소스의 실제 내용을 전부 받아오지 않고
Content-Type
Content-Length
Last-Modified
같은 Header 정보만 확인하고 싶을 때 사용할 수 있다.
GET
→ Header + Body
HEAD
→ Header만
이라고 기억하면 된다.
OPTIONS는 대상 리소스와 통신할 때 사용할 수 있는 옵션을 확인하는 데 사용할 수 있다.
예를 들어 서버가 어떤 HTTP Method를 허용하는지 확인하는 용도로 사용할 수 있다.
GET
POST
PUT
PATCH
DELETE
...
실제 웹 개발에서는 CORS와 관련해서 OPTIONS 요청을 보게 되는 경우도 있다.
HTTP에는
CONNECT
TRACE
같은 Method도 존재한다.
하지만 일반적인 웹 API 개발에서는 직접 사용할 일이 많지 않다.
현재 단계에서는
"이런 Method도 존재한다."
정도로 알아두고 넘어가도 될 것 같다.
지금까지 본 내용을 한 번 정리해보자.
| Method | 주요 용도 |
|---|---|
| GET | 리소스 조회 |
| POST | 요청 데이터 처리, 주로 신규 등록 및 프로세스 처리 |
| PUT | 리소스 전체 대체, 없으면 생성 가능 |
| PATCH | 리소스 부분 변경 |
| DELETE | 리소스 삭제 |
| HEAD | GET과 비슷하지만 Body 제외 |
| OPTIONS | 대상 리소스의 통신 옵션 확인 |
회원 API로 보면 다음과 같다.
GET /members
→ 회원 목록 조회
POST /members
→ 회원 등록
GET /members/{id}
→ 회원 조회
PUT /members/{id}
→ 회원 전체 대체
PATCH /members/{id}
→ 회원 일부 수정
DELETE /members/{id}
→ 회원 삭제
처음에 봤던
/getMembers
/createMember
/updateMember
/deleteMember
와 비교하면 역할이 훨씬 명확하게 분리된다.
URI
→ 회원이라는 Resource
Method
→ 회원에게 수행할 Action
HTTP Method를 공부하다 보면 자연스럽게 다음 질문이 생긴다.
"클라이언트가 서버에게 데이터를 전달하려면 어디에 넣어야 하지?"
강의에서는 크게 두 가지를 먼저 구분한다.
Query Parameter
Message Body
일반적으로
GET
→ Query Parameter
POST / PUT / PATCH
→ Message Body
형태를 많이 사용한다.
예를 들어 검색 요청은
GET /search?q=hello&sort=date
처럼 Query Parameter를 사용한다.
반면 회원 생성은
POST /members
Content-Type: application/json
{
"username": "daehyun",
"age": 27
}
처럼 Message Body를 사용한다.
웹에서 클라이언트가 서버로 데이터를 전달하는 상황은 크게 다음처럼 생각할 수 있다.
정적 데이터 조회
동적 데이터 조회
HTML Form 데이터 전송
HTTP API 데이터 전송
이미지나 정적 HTML 파일처럼 별도의 검색 조건이 필요하지 않은 리소스를 조회한다고 해보자.
GET /static/star.jpg HTTP/1.1
이 경우에는 굳이 Query Parameter가 필요하지 않다.
리소스 경로 자체만으로 어떤 데이터를 원하는지 알 수 있기 때문이다.
/static/star.jpg
검색이나 게시판 목록처럼 조건에 따라 결과가 달라진다면 Query Parameter를 사용할 수 있다.
GET /search?q=hello&hl=ko HTTP/1.1
서버는
q=hello
hl=ko
를 이용해 결과를 필터링하거나 정렬한 뒤 동적으로 응답을 생성한다.
HTML에는 <form>을 이용해서 데이터를 서버로 전달하는 방식이 있다.
예를 들어
<form action="/save" method="post">
<input type="text" name="username">
<input type="text" name="age">
<button type="submit">전송</button>
</form>
을 전송하면 브라우저가 다음과 비슷한 HTTP 요청을 만든다.
POST /save HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=kim&age=20
일반적인 HTML Form에서는
application/x-www-form-urlencoded
형태를 사용할 수 있다.
그리고 파일까지 함께 전송한다면
multipart/form-data
를 사용할 수 있다.
Form에서도 GET 전송이 가능하다.
<form action="/members" method="get">
이라면
GET /members?username=kim&age=20
처럼 Query Parameter 형태로 전달된다.
다만 여기서 중요한 점이 있다.
GET은 조회에 사용해야 한다.
GET 요청으로
회원 등록
회원 삭제
데이터 수정
처럼 서버 리소스를 변경하도록 설계하면 안 된다.
웹 프론트엔드나 모바일 앱, 서버 간 통신에서는 HTML Form보다 HTTP API 형태를 많이 사용한다.
예를 들어
POST /members HTTP/1.1
Content-Type: application/json
{
"username": "daehyun",
"age": 27
}
처럼 JSON을 Message Body에 넣어 전달한다.
대표적인 상황은
서버 ↔ 서버
iOS / Android ↔ 서버
React / Vue ↔ Backend API
등이다.
회원 API와 파일 관리 API를 비교하면 재미있는 차이가 하나 있다.
먼저 회원 등록을 보자.
POST /members
클라이언트는 새 회원의 ID를 모른다.
서버가
100
이라는 ID를 만들고
/members/100
이라는 새로운 URI를 만들어준다.
이런 형태를 Collection이라고 한다.
Collection
서버가 관리하는 리소스 디렉터리
서버가 새로운 리소스 URI를 생성하고 관리
예:
/members
이번에는 파일 관리 시스템을 생각해보자.
PUT /files/star.jpg
여기서는 클라이언트가 이미
/files/star.jpg
라는 URI를 알고 있다.
즉 클라이언트가 저장 위치와 이름을 결정한다.
이런 형태를 Store라고 한다.
Store
클라이언트가 관리하는 리소스 저장소
클라이언트가 URI를 알고 직접 지정
예:
/files
Collection과 Store의 차이를 한 번에 보면 다음과 같다.
Collection
POST /members
↓
서버가 URI 결정
↓
/members/100
Store
PUT /files/star.jpg
↓
클라이언트가 URI 직접 결정
Document는 단일 리소스 하나를 의미한다.
예를 들어
/members/100
/files/star.jpg
처럼 특정 회원 하나나 파일 하나를 식별한다.
정리하면
Document
→ 단일 리소스
Collection
→ 서버가 URI를 관리하는 리소스 집합
Store
→ 클라이언트가 URI를 관리하는 리소스 저장소
이다.
가능하면 URI에는 리소스를 표현하고 Method로 행위를 구분하는 것이 좋다.
그런데 현실에서는 모든 동작을 깔끔한 CRUD 형태로 표현하기 어려울 때도 있다.
특히 순수 HTML Form에서는
GET
POST
만 지원하기 때문에
PATCH
DELETE
같은 Method를 직접 사용하기 어렵다.
그럴 때
/members/100/edit
/members/100/delete
처럼 동사가 포함된 URI를 사용하는 경우가 있다.
이런 형태를 Controller URI라고 한다.
또 HTTP API에서도 단순 CRUD로 표현하기 어려운 특정 프로세스를 실행해야 한다면 Controller URI를 사용할 수 있다.
즉
리소스 중심 URI 설계를 우선하되, 표현하기 어려운 프로세스에서는 Controller URI를 사용할 수 있다.
정도로 이해하면 될 것 같다.
HTTP Method 자체의 의미를 이해했다면 다음으로 중요한 것이 있다.
안전(Safe)
멱등(Idempotent)
캐시 가능(Cacheable)
이다.
처음 들었을 때 특히
안전
멱등
이 둘이 상당히 비슷하게 느껴졌다.
하지만 완전히 다른 개념이다.
안전한 Method는 요청을 수행해도 대상 리소스의 상태를 변경하지 않는 Method다.
대표적으로 GET이 있다.
GET /members/100
을 한 번 호출했다고 해서
100번 회원의 이름이 변경되거나
회원이 삭제되거나
새로운 회원이 생기면
안 된다.
조회만 하는 것이다.
따라서 GET은 Safe Method다.
수업에서 재미있는 질문이 나왔다.
GET을 계속 호출하면 서버에 로그가 쌓일 수 있다.
극단적으로는 너무 많은 요청 때문에 서버 장애가 발생할 수도 있다.
그렇다면 GET은 정말 안전한 것일까?
여기서 안전이라는 개념의 범위를 이해해야 한다.
Safe는 요청 대상 리소스 자체의 상태 변화 여부를 기준으로 본다.
로그가 쌓이거나 통계가 기록되는 등의 부수 효과까지 포함해서 세상의 모든 상태가 전혀 바뀌지 않는다는 의미는 아니다.
즉
GET /members/100
으로 회원 데이터 자체가 변경되지 않는다면 HTTP 의미상 Safe라고 볼 수 있다.
멱등은 처음 들으면 단어부터 어렵다.
그런데 뜻은 생각보다 단순하다.
같은 요청을 한 번 보내든 여러 번 보내든 의도한 최종 상태가 같다면 멱등이다.
예를 들어 조회 요청을 생각해보자.
GET /members/100
한 번 조회하든
GET
GET
GET
GET
여러 번 조회하든 요청 자체가 리소스를 변경하지 않는다.
그래서 GET은 멱등이다.
PUT은 리소스를 특정 상태로 대체한다.
예를 들어
PUT /members/100
{
"username": "kim",
"age": 20
}
을 여러 번 호출한다고 해보자.
1회
→ username=kim, age=20
2회
→ username=kim, age=20
100회
→ username=kim, age=20
최종 결과는 같다.
따라서 PUT은 멱등이다.
100번 회원을 삭제한다고 해보자.
DELETE /members/100
한 번 요청하면 회원이 삭제된다.
다시 요청한다고 해도
100번 회원이 없는 상태
라는 최종 결과 자체는 같다.
응답 Status Code는 달라질 수도 있지만 리소스의 최종 상태 관점에서는 동일하다.
그래서 DELETE 역시 멱등이다.
예를 들어 결제 요청을 생각해보자.
POST /payments
한 번 호출하면 한 번 결제될 수 있다.
두 번 호출하면
결제 2번
이 될 수도 있다.
따라서 POST는 일반적으로 멱등하지 않다.
GET
→ 멱등
PUT
→ 멱등
DELETE
→ 멱등
POST
→ 일반적으로 멱등하지 않음
정도로 정리할 수 있다.
여기서 둘을 확실히 구분해야 한다.
Safe
→ 호출이 리소스 상태를 변경하는가?
Idempotent
→ 같은 요청을 여러 번 반복했을 때
최종 상태가 같은가?
예를 들어 DELETE는
리소스를 삭제한다.
따라서 Safe하지 않다.
하지만
한 번 삭제
100번 삭제
→ 결국 리소스가 없는 상태
이므로 멱등하다.
이 예시가 둘의 차이를 이해하기 가장 좋은 것 같다.
멱등성은 자동 복구와 재요청에서 중요하다.
예를 들어 클라이언트가 서버에 요청했다.
Client
│
│ PUT /members/100
▼
Server
서버에서는 요청을 정상 처리했다.
그런데 네트워크 문제로 응답이 클라이언트에 도착하지 않았다.
클라이언트 입장에서는 고민된다.
"서버가 처리한 거야? 안 한 거야?"
이때 같은 요청을 다시 보내도 최종 결과가 같다면 재요청하기가 상대적으로 안전하다.
요청
↓
Timeout
↓
같은 요청 재시도
이런 자동 복구 메커니즘에서 멱등성이 중요하다.
예를 들어 내가 GET을 했다.
GET /members/100
→ age = 20
그 사이 다른 사용자가 데이터를 바꿨다.
age = 30
내가 다시 GET하면 결과는 달라진다.
GET /members/100
→ age = 30
그러면 GET이 멱등하지 않은 것일까?
아니다.
멱등성은 외부 요인으로 리소스 상태가 변경되는 것까지 고려하는 개념은 아니다.
같은 요청을 반복했을 때 그 요청 자체가 만들어내는 효과를 기준으로 판단한다.
마지막은 캐시 가능성이다.
캐시는 이전에 받은 응답을 저장해두었다가 같은 요청에 다시 활용하는 것이다.
예를 들어 웹페이지에서 큰 이미지를 받았다고 해보자.
Client
↓
이미지 요청
↓
Server
↓
10MB 이미지 응답
같은 이미지를 다시 볼 때마다 서버에서 10MB를 다시 받을 필요가 있을까?
브라우저가 이전에 받은 데이터를 저장하고 있다면 다시 활용할 수 있다.
첫 요청
Server → Image → Browser Cache
두 번째 요청
Browser Cache 사용
이것이 캐시의 기본적인 개념이다.
강의에서는 실무적으로 주로
GET
HEAD
정도를 캐시에 활용한다고 설명한다.
특히 GET은 URL을 Cache Key로 활용하기도 쉬워서 웹에서 캐시와 잘 맞는다.
POST 같은 Method도 HTTP 규칙상 특정 조건에서는 캐시가 가능할 수 있지만, 요청 Body와 응답의 캐시 정책 등을 함께 고려해야 하므로 일반적인 웹 개발에서는 GET만큼 널리 사용되지는 않는다.
현재 단계에서는
실무에서는 GET 기반 캐시를 가장 자주 접한다.
정도로 기억해두려고 한다.
개념적으로 정리하면 다음과 같다.
| Method | Safe | Idempotent | 주요 용도 |
|---|---|---|---|
| GET | O | O | 조회 |
| HEAD | O | O | Header 조회 |
| POST | X | X | 신규 등록 / 프로세스 처리 |
| PUT | X | O | 전체 대체 |
| PATCH | X | 경우에 따라 다름 | 부분 변경 |
| DELETE | X | O | 삭제 |
여기서 특히 기억해야 할 것은
Safe
≠
Idempotent
라는 점이다.
DELETE가 가장 좋은 예다.
DELETE
리소스를 변경함
→ Safe X
반복해도 최종 상태는 동일
→ Idempotent O
처음에는 이렇게 만들 수도 있었다.
/getMembers
/getMember
/createMember
/updateMember
/deleteMember
그런데 HTTP의 리소스와 Method를 이해하고 나면 다음처럼 설계할 수 있다.
GET /members
POST /members
GET /members/{id}
PUT /members/{id}
PATCH /members/{id}
DELETE /members/{id}
여기서 핵심은 URI가
조회
등록
수정
삭제
를 표현하지 않는다는 것이다.
URI가 표현하는 것은
members
라는 리소스다.
그리고
GET
POST
PUT
PATCH
DELETE
가 해당 리소스에 수행할 행동을 표현한다.
즉
URI
→ Resource
HTTP Method
→ Action
이라는 역할 분리가 이루어진다.
처음에는
"URL에 동사를 넣으면 직관적이고 좋은 거 아닌가?"
라고 생각할 수도 있었다.
그런데 HTTP Method 자체가 이미 행위를 표현하고 있다는 걸 알고 나니 굳이 URI가 같은 역할까지 할 필요가 없다는 것이 이해됐다.
이번에는 API URI를 어떻게 설계하는지부터 주요 HTTP Method와 Method의 속성까지 정리해봤다.
가장 먼저 기억해야 할 것은
URI 설계에서 가장 중요한 것은 리소스를 식별하는 것이다.
라는 점이다.
회원 관리 시스템이라면 리소스는
조회
등록
수정
삭제
가 아니라
회원
이다.
그래서 URI에는
/members
/members/100
처럼 회원 리소스를 표현한다.
행위는 HTTP Method가 담당한다.
GET
→ 조회
POST
→ 요청 데이터 처리 / 신규 등록
PUT
→ 전체 대체
PATCH
→ 부분 수정
DELETE
→ 삭제
이렇게 리소스와 행위를 분리해서 생각하면 API URI도 훨씬 단순해진다.
또 POST와 PUT의 차이도 단순히
POST = 등록
PUT = 수정
으로 외우면 부족하다.
POST 기반 Collection
POST /members
→ 서버가 새로운 URI 결정
PUT 기반 Store
PUT /files/star.jpg
→ 클라이언트가 URI 결정
이라는 차이까지 이해하면 둘의 역할이 조금 더 명확해진다.
그리고 Method 자체에도
Safe
Idempotent
Cacheable
이라는 속성이 존재한다.
특히 이번에 가장 헷갈렸던 것은 Safe와 Idempotent였다.
처음에는 둘 다
"여러 번 호출해도 괜찮다는 뜻인가?"
정도로 비슷하게 느껴졌다.
하지만 실제로는 완전히 다른 질문이었다.
Safe
→ 이 요청이 리소스를 변경하는가?
Idempotent
→ 이 요청을 여러 번 반복해도 최종 결과가 같은가?
DELETE처럼
Safe X
Idempotent O
인 Method도 있다는 것을 보면 차이가 확실히 보인다.
결국 HTTP API를 설계할 때 중요한 것은 URI 하나만 잘 만드는 것이 아니었다.
어떤 Resource인가?
↓
어떤 URI로 식별할 것인가?
↓
어떤 HTTP Method를 사용할 것인가?
↓
데이터는 Query Parameter로 전달할 것인가?
Message Body로 전달할 것인가?
↓
이 Method는 Safe한가?
Idempotent한가?
Cacheable한가?
를 함께 생각해야 했다.
예전에는
/api/getMember
/api/createMember
처럼 URL 하나에 모든 의미를 넣는 것이 오히려 직관적이라고 생각했다.
그런데 HTTP가 이미 Method라는 행위 표현 수단을 가지고 있다는 것을 알고 나니
Resource는 URI로 식별하고
행위는 HTTP Method로 표현한다.
라는 설계가 왜 나오는지 조금씩 이해가 되는 것 같다.
다음에는 이 HTTP Method들을 실제 API에서 어떻게 활용하는지,
특히
HTML Form
HTTP API
POST 기반 Collection
PUT 기반 Store
Controller URI
같은 실제 설계 방식도 조금 더 정리해봐야겠다.