HTTP API는 어떻게 설계해야 할까? | 리소스 식별부터 HTTP 메서드까지

대현·5일 전

네트워크

목록 보기
4/5
post-thumbnail

HTTP API는 어떻게 설계해야 할까? | 리소스 식별부터 HTTP 메서드까지

지난 글에서는 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는 각각 어떤 의미인지

안전 / 멱등 / 캐시 가능은 무엇인지

를 하나씩 연결해서 정리해보려고 한다.


URI에는 무엇을 넣어야 할까?

회원 관리 시스템을 다시 생각해보자.

회원 목록 조회
회원 조회
회원 등록
회원 수정
회원 삭제

여기서 리소스는 무엇일까?

처음에는

회원 조회

회원 등록

회원 수정

같은 기능 자체가 리소스라고 생각하기 쉽다.

그런데 그렇지 않다.

여기서 리소스는 회원(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에는 무엇이 있을까?

HTTP에는 여러 Method가 존재한다.

대표적인 Method는 다음과 같다.

GET
POST
PUT
PATCH
DELETE

이외에도

HEAD
OPTIONS
CONNECT
TRACE

등이 있다.

이번에는 웹 개발에서 자주 사용하는 주요 Method부터 살펴보자.


GET — 리소스를 조회한다

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

GET으로 데이터를 전달하고 싶다면?

조회할 때도 서버에게 추가적인 조건을 전달해야 하는 경우가 있다.

예를 들어 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도 기술적으로 Message Body를 포함할 수 있는 경우가 있다.

하지만 실무에서는 거의 사용하지 않는 편이다.

서버나 중간 장비가 GET Body를 지원하지 않는 경우도 있고 상호운용성이 좋지 않기 때문이다.

그래서 일반적으로 GET에서 데이터를 전달할 때는

Query Parameter

를 사용한다고 이해하는 것이 좋다.


POST — 요청 데이터를 처리해줘

다음은 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를

POST = Create

라고 외우면 간단해 보인다.

하지만 POST의 역할은 그것보다 넓다.

POST는 요청 데이터를 서버가 정의한 방식으로 처리하게 할 수 있다.

대표적으로

HTML Form 회원가입

상품 주문

게시글 작성

댓글 등록

결제 요청

프로세스 실행

등에 사용할 수 있다.

즉 신규 리소스 생성뿐만 아니라 단순한 CRUD로 표현하기 어려운 프로세스 처리에도 사용할 수 있다.

예를 들어

POST /orders/100/cancel

처럼 특정 주문을 취소하는 프로세스를 실행하는 식으로 사용할 수도 있다.

이런 URI를 Controller URI 또는 Control URI 형태로 볼 수 있다.

REST스럽게 리소스와 Method만으로 표현하기 어려운 동작에서 사용할 수 있다.


POST에는 반드시 Body가 있어야 할까?

처음 메모에는

POST는 무조건 데이터를 Body에 담아서 보내야 한다.

라고 적어두었다.

그런데 조금 더 정확하게 말하면 POST라고 해서 반드시 Message Body가 존재해야 하는 것은 아니다.

POST의 핵심은

대상 리소스에 요청 데이터를 전달하여 리소스가 정의한 방식으로 처리하도록 요청하는 것

이다.

실제 웹 API에서는 JSON 등의 Body를 전달하는 경우가 많기 때문에

POST
→ Message Body

를 자주 보게 되는 것이다.


POST를 한 문장으로 정리하면

POST는 활용 범위가 꽤 넓다.

그래서 다음 정도로 기억하면 좋을 것 같다.

POST는 서버에게 요청 데이터를 전달하고, 대상 리소스가 정의한 방식대로 처리하도록 요청하는 Method다.

대표적으로

새로운 리소스 생성

프로세스 실행

다른 HTTP Method로 표현하기 애매한 처리

등에 사용할 수 있다.


PUT — 리소스를 통째로 대체한다

PUT은 처음 배울 때 꽤 헷갈렸다.

보통

PUT = 수정

이라고 외우기 쉽기 때문이다.

그런데 PUT의 핵심은 단순 수정이 아니다.

대상 리소스를 요청 데이터로 완전히 대체한다.

파일을 폴더에 복사하는 상황을 생각하면 이해하기 쉽다.

기존 파일 존재
+
같은 이름의 새 파일 복사

↓

기존 파일을 새 파일로 덮어씀

PUT도 비슷하다.

예를 들어

PUT /members/100

으로 다음 데이터를 보냈다고 해보자.

{
    "username": "daehyun"
}

기존 100번 회원 데이터가

{
    "username": "daehyun",
    "age": 27
}

이었다면 PUT의 의미는

age만 그대로 두고
username만 수정

이 아니다.

요청으로 전달된 표현으로 리소스 전체를 대체하는 것에 가깝다.

따라서 결과가

{
    "username": "daehyun"
}

처럼 될 수 있다.

그래서 PUT을 사용할 때는 주의해야 한다.


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은 지정한 위치에 리소스가 없으면 새롭게 생성할 수도 있다.

PUT /files/star.jpg

요청을 보냈는데

/files/star.jpg

가 존재하지 않는다면 새로운 리소스를 생성할 수 있다.

따라서 PUT은

리소스가 있으면
→ 완전히 대체

리소스가 없으면
→ 생성

이라고 기억할 수 있다.


PUT은 수정 Method가 아니었네?

여기까지 보고 나니 처음 생각했던

PUT = 수정

이라는 설명이 조금 애매하다는 것을 알게 됐다.

PUT은 더 정확하게

리소스 전체 대체

에 가깝다.

그러면 이런 생각이 든다.

"나는 회원의 이름만 바꾸고 싶은데?"

이때 사용하는 것이 PATCH다.


PATCH — 리소스를 부분 변경한다

PATCH는 대상 리소스의 일부를 변경할 때 사용한다.

예를 들어 기존 데이터가 다음과 같다고 해보자.

{
    "username": "daehyun",
    "age": 27
}

이름만 수정하고 싶다면

PATCH /members/100

과 함께

{
    "username": "newName"
}

을 전달할 수 있다.

그러면

username
→ 변경

age
→ 유지

처럼 필요한 부분만 수정할 수 있다.

그래서 다음처럼 구분하면 기억하기 쉽다.

PUT
→ 전체 대체

PATCH
→ 부분 변경

만약 서버나 클라이언트 환경에서 PATCH를 사용하기 어려운 경우에는 POST를 이용해 변경 기능을 구현하는 경우도 있다.


DELETE — 리소스를 삭제한다

DELETE는 이름 그대로 리소스를 삭제한다.

예를 들어

DELETE /members/100

100번 회원 리소스를 삭제해줘.

라는 요청이다.

Client
   │
   │ DELETE /members/100
   ▼
Server
   │
   │ 100번 회원 삭제
   ▼
삭제 완료

다른 Method에 비해 의미가 상당히 직관적이다.


HEAD

HEAD는 GET과 비슷하다.

하지만 서버는 응답에서 Message Body를 제외하고 Status Line과 Header만 반환한다.

예를 들어 리소스의 실제 내용을 전부 받아오지 않고

Content-Type

Content-Length

Last-Modified

같은 Header 정보만 확인하고 싶을 때 사용할 수 있다.

GET
→ Header + Body

HEAD
→ Header만

이라고 기억하면 된다.


OPTIONS

OPTIONS는 대상 리소스와 통신할 때 사용할 수 있는 옵션을 확인하는 데 사용할 수 있다.

예를 들어 서버가 어떤 HTTP Method를 허용하는지 확인하는 용도로 사용할 수 있다.

GET
POST
PUT
PATCH
DELETE
...

실제 웹 개발에서는 CORS와 관련해서 OPTIONS 요청을 보게 되는 경우도 있다.


CONNECT와 TRACE

HTTP에는

CONNECT
TRACE

같은 Method도 존재한다.

하지만 일반적인 웹 API 개발에서는 직접 사용할 일이 많지 않다.

현재 단계에서는

"이런 Method도 존재한다."

정도로 알아두고 넘어가도 될 것 같다.


HTTP Method 전체 정리

지금까지 본 내용을 한 번 정리해보자.

Method주요 용도
GET리소스 조회
POST요청 데이터 처리, 주로 신규 등록 및 프로세스 처리
PUT리소스 전체 대체, 없으면 생성 가능
PATCH리소스 부분 변경
DELETE리소스 삭제
HEADGET과 비슷하지만 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 API에서 데이터를 어떻게 전달할까?

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으로 데이터를 전송하면?

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

를 사용할 수 있다.


HTML Form에서 GET도 사용할 수 있다

Form에서도 GET 전송이 가능하다.

<form action="/members" method="get">

이라면

GET /members?username=kim&age=20

처럼 Query Parameter 형태로 전달된다.

다만 여기서 중요한 점이 있다.

GET은 조회에 사용해야 한다.

GET 요청으로

회원 등록

회원 삭제

데이터 수정

처럼 서버 리소스를 변경하도록 설계하면 안 된다.


HTTP API에서는 JSON을 많이 사용한다

웹 프론트엔드나 모바일 앱, 서버 간 통신에서는 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

등이다.


URI 설계에는 Collection과 Store라는 개념도 있다

회원 API와 파일 관리 API를 비교하면 재미있는 차이가 하나 있다.

먼저 회원 등록을 보자.

POST /members

클라이언트는 새 회원의 ID를 모른다.

서버가

100

이라는 ID를 만들고

/members/100

이라는 새로운 URI를 만들어준다.

이런 형태를 Collection이라고 한다.

Collection

서버가 관리하는 리소스 디렉터리

서버가 새로운 리소스 URI를 생성하고 관리

예:

/members

Store는 무엇일까?

이번에는 파일 관리 시스템을 생각해보자.

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는 무엇일까?

Document는 단일 리소스 하나를 의미한다.

예를 들어

/members/100

/files/star.jpg

처럼 특정 회원 하나나 파일 하나를 식별한다.

정리하면

Document
→ 단일 리소스

Collection
→ 서버가 URI를 관리하는 리소스 집합

Store
→ 클라이언트가 URI를 관리하는 리소스 저장소

이다.


Controller 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에는 속성도 있다

HTTP Method 자체의 의미를 이해했다면 다음으로 중요한 것이 있다.

안전(Safe)

멱등(Idempotent)

캐시 가능(Cacheable)

이다.

처음 들었을 때 특히

안전
멱등

이 둘이 상당히 비슷하게 느껴졌다.

하지만 완전히 다른 개념이다.


안전(Safe)이란?

안전한 Method는 요청을 수행해도 대상 리소스의 상태를 변경하지 않는 Method다.

대표적으로 GET이 있다.

GET /members/100

을 한 번 호출했다고 해서

100번 회원의 이름이 변경되거나

회원이 삭제되거나

새로운 회원이 생기면

안 된다.

조회만 하는 것이다.

따라서 GET은 Safe Method다.


그런데 GET을 100만 번 요청하면 서버 로그가 쌓이지 않을까?

수업에서 재미있는 질문이 나왔다.

GET을 계속 호출하면 서버에 로그가 쌓일 수 있다.

극단적으로는 너무 많은 요청 때문에 서버 장애가 발생할 수도 있다.

그렇다면 GET은 정말 안전한 것일까?

여기서 안전이라는 개념의 범위를 이해해야 한다.

Safe는 요청 대상 리소스 자체의 상태 변화 여부를 기준으로 본다.

로그가 쌓이거나 통계가 기록되는 등의 부수 효과까지 포함해서 세상의 모든 상태가 전혀 바뀌지 않는다는 의미는 아니다.

GET /members/100

으로 회원 데이터 자체가 변경되지 않는다면 HTTP 의미상 Safe라고 볼 수 있다.


멱등(Idempotent)이란?

멱등은 처음 들으면 단어부터 어렵다.

그런데 뜻은 생각보다 단순하다.

같은 요청을 한 번 보내든 여러 번 보내든 의도한 최종 상태가 같다면 멱등이다.

예를 들어 조회 요청을 생각해보자.

GET /members/100

한 번 조회하든

GET
GET
GET
GET

여러 번 조회하든 요청 자체가 리소스를 변경하지 않는다.

그래서 GET은 멱등이다.


PUT은 왜 멱등일까?

PUT은 리소스를 특정 상태로 대체한다.

예를 들어

PUT /members/100

{
    "username": "kim",
    "age": 20
}

을 여러 번 호출한다고 해보자.

1회
→ username=kim, age=20

2회
→ username=kim, age=20

100회
→ username=kim, age=20

최종 결과는 같다.

따라서 PUT은 멱등이다.


DELETE도 멱등이다

100번 회원을 삭제한다고 해보자.

DELETE /members/100

한 번 요청하면 회원이 삭제된다.

다시 요청한다고 해도

100번 회원이 없는 상태

라는 최종 결과 자체는 같다.

응답 Status Code는 달라질 수도 있지만 리소스의 최종 상태 관점에서는 동일하다.

그래서 DELETE 역시 멱등이다.


POST는 왜 멱등이 아닐까?

예를 들어 결제 요청을 생각해보자.

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이 멱등하지 않은 것일까?

아니다.

멱등성은 외부 요인으로 리소스 상태가 변경되는 것까지 고려하는 개념은 아니다.

같은 요청을 반복했을 때 그 요청 자체가 만들어내는 효과를 기준으로 판단한다.


캐시 가능(Cacheable)

마지막은 캐시 가능성이다.

캐시는 이전에 받은 응답을 저장해두었다가 같은 요청에 다시 활용하는 것이다.

예를 들어 웹페이지에서 큰 이미지를 받았다고 해보자.

Client
        ↓
이미지 요청
        ↓
Server
        ↓
10MB 이미지 응답

같은 이미지를 다시 볼 때마다 서버에서 10MB를 다시 받을 필요가 있을까?

브라우저가 이전에 받은 데이터를 저장하고 있다면 다시 활용할 수 있다.

첫 요청
Server → Image → Browser Cache

두 번째 요청
Browser Cache 사용

이것이 캐시의 기본적인 개념이다.


어떤 Method를 캐시할까?

강의에서는 실무적으로 주로

GET

HEAD

정도를 캐시에 활용한다고 설명한다.

특히 GET은 URL을 Cache Key로 활용하기도 쉬워서 웹에서 캐시와 잘 맞는다.

POST 같은 Method도 HTTP 규칙상 특정 조건에서는 캐시가 가능할 수 있지만, 요청 Body와 응답의 캐시 정책 등을 함께 고려해야 하므로 일반적인 웹 개발에서는 GET만큼 널리 사용되지는 않는다.

현재 단계에서는

실무에서는 GET 기반 캐시를 가장 자주 접한다.

정도로 기억해두려고 한다.


HTTP Method 속성을 한 번에 정리해보자

개념적으로 정리하면 다음과 같다.

MethodSafeIdempotent주요 용도
GETOO조회
HEADOOHeader 조회
POSTXX신규 등록 / 프로세스 처리
PUTXO전체 대체
PATCHX경우에 따라 다름부분 변경
DELETEXO삭제

여기서 특히 기억해야 할 것은

Safe
≠
Idempotent

라는 점이다.

DELETE가 가장 좋은 예다.

DELETE

리소스를 변경함
→ Safe X

반복해도 최종 상태는 동일
→ Idempotent O

처음 API를 설계했던 방식으로 다시 돌아가보자

처음에는 이렇게 만들 수도 있었다.

/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

같은 실제 설계 방식도 조금 더 정리해봐야겠다.

profile
도전을 멈추지 않는 개발자

0개의 댓글