프로젝트 일지

[💡 일지] RESTful API Design

Kamea 2022. 5. 31. 13:51

# 인트로 : 중요한 내용 아님

더보기

나는 변수 네이밍에 꽤 많은 시간과 관심을 들이는 편이다.

1. 협업을 할 때 나를 제외한 다른 사람도 알아챌 수 있어야 하며

2. 서비스 전체 틀 구조에서의 통일성을 가져야 한다는 이유 때문이다.

 

현재 프로젝트는 프론트엔드를 혼자 해서 별 상관이 없지만, 백엔드에서 주는 api url 과 변수명에 조금의 혼돈이 왔다. 약간 혼자 예민보스인 부분...이걸 말씀드릴까 하다가 나는 백엔드 지식 0이라 무작정 말씀드릴 순 없어서 공부를 하고 제안을 해보기로 했다.

 

오히려 좋아

 

 

내가 어제 밤에 따끈따끈하게 전달받은 url 이다. 한눈에 보기에도 알아차리기 어려우며 프론트에게 혼란을 준다. 변수명은 영어의 뜻과 맞지 않는 변수가 가끔 보인다. 데이터베이스가 완전히 구축되기 전에 클라이언트에 test 파일을 만들어서 먼저 변수를 내가 맞다고 생각하는 대로 사용을 하고 있었다. 대표적으로 나는 gender, 백엔드에서는 petSex, 나는 breeds, 백엔드에서는 petSpecies가 있다.

 

함께 변수명을 정했어야 하는데 소통이 없었던 탓에 이런 결과가 나타났다..ㅎ 이건 100% 내 잘못... 저 위의 변수명이 뭐가 다르냐고 말하는 사람들이 있겠지만, 팀에서 영어를 좀 한다 하는 사람이 들으면 약간 그 사람도 혼돈이 올 것이다. 이래서 개발자도 영어 공부해야 하는 듯하다.

 

 

 

 

 

 

# 본론

아무것도 모르는데 내 눈에만 이상해 보인다고 무작정 바꿔달라고 할 수는 절대로 없기에 공부를 해보기로 했다.

근데 알다시피 naming convention에는 이게 정답이야!!!!!! 하고 정해져 있는 것은 없고 추천, 많이 통용되는 것만 존재한다.

이 포스트도 정답은 아니라는 소리이다.

 

👇🏻 포스트 참고 url

https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design

 

Web API design best practices - Azure Architecture Center

Learn the best practices for designing web APIs that support platform independence and service evolution.

docs.microsoft.com

 

2022년 4월 7일에 나온 앗 뜨거운 문서이다. 쏘 핫.

 

 

 

 

 

 

💡 잘 설계된 web API 가 지원해야 하는 것

1. Platform independence 

클라이언트는 내부적으로 API가 어떻게 설게 되어 있든지 API를 호출(접근)할 수 있어야 한다. 이것을 위해서는 표준 프로토콜 사용, 데이터 포맷에 대해 클라이언트와 웹 서비스가 모두 이해할 수 있는 방식을 가지고 있어야 한다.

한 마디로, 지킬 것(프로토콜, CORS) 잘 지키고 UX에 기반한 API를 설계하라는 뜻으로 들린다.

 

2. Service evolution 

만약, 서비스가 진화(발전)함에 따라 API 변경이 필요할 때, 클라이언트 어플리케이션과 독립적으로 진화가 되어야 한다. 

한마디로, 서비스 다음 버전이 출시가 되더라도 사용자가 API의 변경을 감지하지 않고도 사용이 가능하게끔 탄탄한 설계가 필요하다. 그게 진짜 어려운 거 아닌감...

 

 

 

 

 

 

 

💡 REST란?

역사는 이렇다. 2000년도에 Roy Fielding이라는 사람이 제안한 웹 서비스 설계 구조적 접근 방식이다. 

REST는 Representational State Transfer의 줄임말이고 직역하면 대표적 상태 전달이다. 무슨 소리?

 

REST는 HTTP에 필수적으로 연관되어 있는 것은 아니지만, 가장 대표가 되는 REST API는 애플리케이션 프로토콜로 HTTP를 쓴다. 

HTTP를 쓰는 이유는 HTTP가 open standards(개방적 표준정도..?)를 사용하고 다른 API 구축에 연관되어 있지 않기 때문이다. 예를 들어, REST 웹 서비스는 ASP.NET에서 읽힐 수 있고 클라이언트 어플리케이션은 HTTP를 요청하고 응답을 전달할 수 있는 어떠한 언어나 툴에서 사용이 가능하다. 

 

REST는 resource(자원, URI), operation(동작, GET, POST,...), representation(표현, 반환 데이터)로 이루어져 있다.  

 

 

 

 

 

 

💡 HTTP를 사용한 RESTful API의 주요 설계 원칙

1. RESTful API는 resource(object, data)를 고려해 디자인한다.

 

2. resource는 그 resource를 독자적으로 정의할 수 있는 URL 식별자를 가지고 있다. 예를 들어, 특정 고객 주문을 확인하는 URL은 아래와 같이 된다.

https://adventure-works.com/orders/1

 

3. 클라이언트가 데이터 교환을 하며 서비스를 사용할 때, 교환 포맷으로 JSON을 사용하는 web API들이 많다. 예를 들어, URL에 GET 요청을 하면 아래의 response body가 날라온다.

{"orderId":1,"orderValue":99.90,"productId":1,"quantity":1}

 

4. 표준 HTTP에서는 resource들이 수행하는 동작들로 GET, POST, PUT, PATCH, DELETE가 있다. 

 

5. REST API들은 representation에 포함된 hypermedia에 의해 구동된다. 무슨 소리냐면, 아래의 주문 JSON representation 에는 해당 주문에 연관된 고객을 불러오거나 수정하는 링크를 포함한다.

{
    "orderID":3,
    "productID":2,
    "quantity":4,
    "orderValue":16.60,
    "links": [
        {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" },
        {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" }
    ]
}

 

 

 

 

 

 

💡 web API의 maturity model (성숙 모델)

Level 0️⃣  URI을 정의하고 모든 동작들은 이 URL에 POST 요청을 보낸다.

Level 1️⃣  각각의 resource들에 분리된 URl을 만든다.

Level 2️⃣  resource들에 HTTP 메소드를 사용한 동작을 정의한다. ← 대부분의 단계 

Level 3️⃣  hypermedia를 사용한다.  ← 지향점

 

마이크로소프트 문서에서는 Level 2가 대부분의 API들의 단계이고 Level 3는 진정한 RESTful API의 의미와 일치한다고 한다.

 

 

 

 

 

 

 

💡 resource에 기반한 API 설계

예를 들어, 온라인 상점 시스템에서 주요 엔티티는 고객들(Customers)과 주문들(Orders)이다. HTTP POST 요청을 이용해 주문 정보를 포함한 주문을 생성한다. HTTP 응답은 주문이 성공적으로 들어갔는지, 아닌지를 반환한다.

가능하다면, resource URI들은 동사가 아닌(resource의 동작, create-order), 명사(resource, orders)을 기반으로 해야한다.

 

https://adventure-works.com/orders // 😄 이거야
https://adventure-works.com/create-order // 😡 하지마
GET /members/delete/1 // 😡 하지마
DELETE /members/1 // 😄 이거야

이 부분에 관해서는 약간의 논란이 있는 것 같다. 
백엔드 아는 사람에게 물어보니 POST로 delete 하는 방식도 빈번하게 사용되는 것 같다.
POST에 삭제시 필요한 데이터를 보내야할 필요가 있다면, POST로 처리하고, 그게 아니라면 DELETE가 맞는 것 같다.
나도 잘 모름?

 

주문 resource는 연관성있는 몇몇의 테이블들에 내부적으로 들어가 있을 것이다.

하지만, 클라이언트에게는 하나의 단일 엔티티로 보여진다.

데이터베이스의 내부 구조를 단순히 보여주는(미러링하는) API를 생성하는 것을 피해라. REST의 목적은 엔티티와 해당 엔티티에 대해 애플리케이션이 수행할 수 있는 작업을 모델링하는 것이다. 클라이언트는 내부 구현에 노출되어서는 안 된다.

 

엔티티들은 종종 orders, customers와 같은 컬렉션으로 그룹화된다.

컬렉션은 컬렉션 내의 항목과 분리된 resource이며 고유한 URI를 가져야 한다. 예를 들어, 아래 URI는 orders 컬렉션을 나타낸다.

 

https://adventure-works.com/orders

 

컬렉션 URI(위 URI)에 HTTP GET 요청을 보낼 때, 컬렉션의 아이템 리스트를 반환한다. 각각의 아이템은 그것의 고유한 URI를 가지고 있고 이 URI로 HTTP GET을 요청하면 아이템의 세부사항을 반환한다. 

 

URI에서 일관된 naming convention을 사용해라. 일반적으로 컬렉션을 참조하는 URI들은 복수명사를 사용한다. 예를 들어, /customers는 customers 컬렉션의 경로를, /customers/5는 ID가 5인 customer의 경로를 나타낸다.

 

이러한 접근을 사용하는 이유는 web API의 직관성을 유지하는데에 도움이 되기 때문이다. 또한 많은 web API 프레임워크는 매개 변수화된 URI 경로를 기반으로 요청을 라우팅할 수 있으므로 /customers/{id}에 대한 경로를 사용해라.

 

또한 다양한 타입의 resource들 사이의 관계성과 어떻게 이 연관성을 나타낼지를 고민해라. /customers/5/orders 는 고객 ID 5번의 모든 주문 내역을 나타낸다. 아니면 /orders/99/customer과 같은 URI로 주문 데이터에 있는 고객을 찾을 수도 있다. 그러나 이 모델을 너무 많이 확장하면 구현하기가 번거로울 수 있다. 더 좋은 방법은 HTTP 응답 메시지 본문에 연결된 자원에 대한 탐색 가능한 링크를 제공하는 것이다.

 

이 방법이 확장되어 복잡한 시스템에서는 클라이언트가 /customers/1/orders/99/products와 같은 여러 수준의 관계를 탐색할 수 있는 URI를 제공하는 것이 매력적일 수 있다. 그러나 이러한 수준의 복잡성은 유지하기가 어려울 수 있으며 향후 리소스 간의 관계가 변경될 경우 유연하지 못하다. 대신 URI를 비교적 단순하게 유지해 응용 프로그램에 리소스에 대한 참조를 사용하여 해당 리소스와 관련된 항목을 찾을 수 있다. 이전 쿼리를 URI /customers/1/orders로 대체하여 고객 1에 대한 모든 주문을 찾은 다음 /orders/99/products를 사용하여 이 주문의 상품을 찾을 수 있다.

 

⭐️ 팁 !
collection/item/collection 이상의 복잡한 resource URI 를 요청하는 것은 피해라

 

 

 

 

 

 

💡 HTTP 메소드 용어에 따른 API 동작 정의(CRUD)

동작명 동작
GET 특정 URI에 있는 자원의 정보를 반환한다. response body 메세지에는 요청된 자원의 세부사항이 들어있다.
POST 특정 URI에 새로운 자원을 생성한다. response body 메세지에는 새 자원의 세부사항이 들어있다. 이 작업은 실제로 자원을 창조하지는 않는 trigger operation에도 사용될 수 있다.
PUT 특정 URI의 자원을 생성하거나 교체한다. response body 메세지에는 생성되었는지나 업데이트 되었는지 명시한다.
PATCH 자원의 부분 업데이트를 수행한다. response body에는 자원에 적용할 변경사항의 집합을 명시한다.
DELETE 특정 URI의 자원을 삭제한다.

 

온라인 상점에 대한 예시는 아래와 같다.

자원 POST GET PUT DELETE
/customers 새로운 고객을 생성 모든 고객을 가져옴 대량의 고객을 업데이트 모든 고객을 삭제
/customers/1 Error 1번 고객의 상세정보를 가져옴 만약 존재한다면 1번 고객의 상세정보를 업데이트 1번 고객을 삭제
/customers/1/orders 1번 고객의 새로운 주문을 생성 1번 고객의 모든 주문 내역을 가져옴 1번 고객의 대량의 주문 내역을 업데이트 1번 고객의 모든 주문 내역을 삭제

 

POST, PUT, PATCH의 차이점은 대부분의 사람들이 혼란스러워한다.

 

POST 자원을 생성한다. 서버는 새로운 자원을 위한 URI를 배정하고 클라이언트에 URI를 반환한다. REST 모델에서 컬렉션에 POST 요청을 자주 보내고 새로운 자원은 컬렉션에 추가가 된다. POST 요청은 새로운 자원이 생성되는 것 말고도 동작 처리를 위해 기존의 자원에 데이터를 보낼 때 사용될 수도 있다.

 

PUT은 자원을 생성하거나 기존의 자원을 업데이트한다. 클라이언트는 자원의 URI를 명시하고 request body는 자원의 전체 데이터를 포함한다. 만약, 해당 URI에 자원이 존재한다면 교체된다. 새로운 자원을 생성한다면 서버는 그렇게 하게 지원한다. PUT 요청은 개개인의 아이템(컬렉션보다는 특정 customer)의 자원에 사용된다. PUT를 통한 생성을 지원할지 여부는 클라이언트가 URI가 존재하기 전에 리소스에 URI를 의미 있게 할당할 수 있는지에 따라 달라진다. 그렇지 않은 경우 POST를 사용하여 리소스를 생성하고 업데이트에는 PUT 또는 PATCH를 사용하라.

 

PATCH는 기존 자원의 부분 업데이트를 수행한다. 클라이언트는 자원의 URI를 명시하고 request body는 자원의 변경 데이터가 적용된 자원의 집합를 포함한다. PUT보다 훨신 효율적인 방법이며 기술적으로 서버가 지원을 해준다면 PATCH는 새로운 자원도 생성이 가능하다.

 

PUT 요청은 멱등성(여러번 요청을 하더라도 결과가 달라지지 않는 속성)을 가지고 있다. 같은 PUT 요청을 클라이언트가 여러번 요청한다면, 결과는 항상 같다. POST, PATCH는 멱등성이 보장되지는 않는다.

 

 

 

 

공식문서 해석하느라 머리가 터질 것 같으니 쉬다가 다시 작성하도록 하겠다!