안녕하세요 지금까지 RESTful API를 잘못 만들고 있던 사람입니다.

Taewoo·2023년 5월 10일
0

Web API design best practices - Azure Architecture Center

출근 길에 지하철에서 마이크로소프트의 API 디자인 관련 게시글을 읽던 중 어디서 본 기억이 있는 익숙한 응답 형식이 눈에 들어왔다.

{
  "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"
    }
  ]
}

links 필드를 어디서 본 적이 있는 것 같아 내가 모르는 어떠한 응답 형식이 있는건가? 라는 궁금증을 갖고 여기저기 찾아보고 알게 된 내용을 정리했다.

REST API가 뭘까? 🤔

REST(Representational State Transfer) API는 웹 기반의 소프트웨어 아키텍처 스타일 중 하나로, 클라이언트와 서버 간의 통신을 위한 인터페이스를 제공한다.

리소스 표현 방식

RESTful API는 자원(Resource)을 URI(Uniform Resource Identifier)로 표현하고, HTTP 메소드(GET, POST, PUT, DELETE 등)를 사용하여 해당 자원을 CRUD(Create, Read, Update, Delete) 작업하는 방식으로 동작한다.

STATELESS

또한 REST API는 상태(State)를 유지하지 않으며, 요청에 대한 응답은 클라이언트가 요청한 리소스의 상태 정보만을 포함하도록 한다.

이러한 특징들은 RESTful API가 단순하고 확장성이 뛰어나며, 다양한 클라이언트와 서버 간의 상호작용을 용이하게 만들어준다.

REST API의 구성 요소

구성 요소내용
자원URI를 통해 표현되며, 클라이언트가 요청하는 대상입니다. 대표적으로 /users, /products 등이 있다.
메소드해당 자원에 대한 작업을 정의한다. GET은 해당 리소스의 조회 , POST는 새로운 리소스를 생성하는 작업을 수행한다.
표현클라이언트와 서버 간에 교환되는 데이터의 형식을 의미한다. 일반적으로 JSON, XML 등을 사용한다

REST API의 장점

  1. 다양한 클라이언트와 서버 간의 상호작용이 용이함
  2. 단순하고 직관적인 인터페이스를 제공하여 개발 및 유지보수가 용이함
  3. 확장성이 뛰어나며, 새로운 기능을 추가하거나 수정하는 데 유연함

REST API의 단점

  1. 요청과 응답이 모두 명확한 구조를 갖추어야 하므로 설계가 복잡할 수 있습니다.
  2. API 버전 관리가 필요합니다.
  3. 보안이 취약할 수 있습니다.

한줄 요약

HTTP 프로토콜의 구성 요소를 잘 사용해 만든 API 설계 방식이다.

나는 백엔드 개발을 하며 아래의 REST 설계 규칙을 지켰다.

리소스 표현 케이스

Good
https://velog.com/ing9990/blog : GET 
https://velog.com/ing9990/blog/123/comments : POST
https://velog.com/ing9990/blog/123/comments/123 : DELETE
Bad
https://velog.com/ing9990/get-blog 
https://velog.com/ing9990/post-blog/get_comment?bid=123
https://velog.com/ing9990/delete-blog?bid=123

응답 코드

응답 코드응답 내용
201리소스의 생성을 응답할 때 사용함
202서버의 latency를 고려해 Async로 처리한 경우 요청은 받아들여졌지만 처리가 완료되지 않았을 경우에 사용했음.
204성공에 대한 응답이지만 본문이 없을 경우 (보통 DELETE 요청에 많이 사용했던것 같음)
404리소스가 존재하지 않은 경우에 사용함.
400요청 본문이 잘못되었을때 사용했음. JSON 객체가 이상하거나 API Spec에 어긋나는 요청을 했을 경우

내가 RESTful API를 잘 이해하고 설계했다고 생각했지만 아래의 글을 읽고 그 생각이 틀렸음을 알게되었다.

Best RESTful API - Microsoft

REST의 주요 동기 중 하나는 URI에 대한 사전 지식 없이 전체 리소스를 탐색할 수 있어야 한다는 것이다.

즉 스펙이나 문서를 제공하지 않아도 클라이언트에서 전체 리소스 세트를 탐색할 수 있다.

link 필드는 현재(self)와 관계(rel)된 API의 엔드포인트를 응답해 클라이언트가 리소스 세트를 탐색할 수 있도록 만든 것이다.

{
// 응답
  "orderID": 3,
  "productID": 2,
  "quantity": 4,
  "orderValue": 16.60,
  
// 연관 API의 엔드포인트
  "links": [
    {
      "rel": "product",
      "href": "https://adventure-works.com/customers/3",
      "action": "GET"
    },
    
    {
      "rel": "product",
      "href": "https://adventure-works.com/customers/3",
      "action": "PUT"
    }
  ]
}

문제는 내가 만들어온 서버를 포함한 RESTful API라 소개된 많은 백엔드 서버들이 이러한 규격을 따르지 않고 있다.

금융 결제원 RESTful API

예시로 금융결제원에 REST API를 보자.

https://openapi.openbanking.or.kr/oauth/2.0/authorize_account
https://openapi.openbanking.or.kr/v2.0/account/update_info

  1. URI에 _ 언더바가 포함된다.
  2. URI에 update라는 행위가 포함된다.
  3. self와 연관된 rel 링크를 제공하지 않아 리소스 파악이 힘들다.

HATEOAS

HATEOAS (Hypermedia as the Engine of Application State)는 REST 아키텍처 스타일의 하나로, RESTful API의 구성 요소 중 하나이다.

즉, RESTful API를 사용할 때 클라이언트가 서버로 요청을 보내면, 서버는 그에 대한 응답으로 데이터 뿐만 아니라 해당 데이터와 관련된 하이퍼미디어 링크를 함께 반환한다.

이 링크는 클라이언트가 다음에 어떤 요청을 보내야 하는지를 알려주는 정보를 포함하고 있다.

HATEOAS를 지킨 예시

예를 들어, 은행 API에서 계좌 조회를 요청하면, 서버는 계좌 정보를 반환하는 동시에 해당 계좌의 거래 내역 조회를 위한 URI 링크를 함께 반환한다.

이 링크를 클릭하면 클라이언트는 새로운 요청을 보내어 거래 내역을 조회할 수 있다.

HATEOAS를 도입해 리팩토링한 예시

# 질문 API에 대한 응답
{
  "status": "OK",
  "data": [
    {
      "questionId": 1,
      "authorUsername": "testuser1",
      "title": "TCP와 UDP의 차이",
      "detail": "TCP와 UDP의 차이를 알려주세요!!"
    },
    {
      "questionId": 2,
      "authorUsername": "testuser2",
      "title": "BFS 알고리즘이 뭔가요?",
      "detail": "알고리즘을 푸는데 BFS가 뭔지 모르겠어요 ㅠㅠ"
    },
    {
      "questionId": 3,
      "authorUsername": "testuser3",
      "title": "파사드 패턴이 뭔가요?",
      "detail": "소프트웨어 디자인 패턴이라는데 뭔지 자세히 알려주세요."
    }
  ],
  "message": ""
}

요청에 대한 응답을 잘했지만 관련된 리소스를 조회하기 위한 링크를 제공하지 않았다.

아래는 HATEOAS를 공부하고 리팩토링한 응답이다.

# 질문 API에 대한 응답
{
  "content": {
    "status": "OK",
    "data": [
      {
        "questionId": 1,
        "authorUsername": "testuser1",
        "title": "TCP와 UDP의 차이",
        "detail": "TCP와 UDP의 차이를 알려주세요!!"
      },
      {
        "questionId": 2,
        "authorUsername": "testuser2",
        "title": "BFS 알고리즘이 뭔가요?",
        "detail": "알고리즘을 푸는데 BFS가 뭔지 모르겠어요 ㅠㅠ"
      },
      {
        "questionId": 3,
        "authorUsername": "testuser3",
        "title": "파사드 패턴이 뭔가요?",
        "detail": "소프트웨어 디자인 패턴이라는데 뭔지 자세히 알려주세요."
      }
    ],
    "message": ""
  },
  "_links": {
    "self": {
      "href": "http://localhost:7878/api/questions",
      "message": "질문 글 전체 조회",
      "method": "GET"
    },
    "question&answers": {
      "href": "http://localhost:7878/api/questions/0/answers",
      "message": "게시글 댓글 조회",
      "method": "GET"
    }
  }
}

만약 게시글 등록에 대한 응답이라면 아래와 같이 리팩토링할 수 있다.

{
# 응답 내용
  "content": {
    "status": "OK",
    "data": {
      "questionId": 3,
      "authorName": "태우",
      "site": "https://velog.com/ing9990"
    }
  },
# 링크 
  "_links": {
  # 현재 호출한 API
    "self": {
      "href": "http://localhost:7878/question",
      "method": "POST",
      "message": "질문글을 등록합니다."
    },
  # 연관된 API
    "rel": [
      {
        "href": "http://localhost:7878/question/3/detail",
        "method": "GET",
        "message": "게시글의 상세 내용을 응답합니다."
      },
      {
        "href" : "http://localhost:7878/question/3",
        "method" : "DELETE",
        "message" : "게시글을 삭제합니다."
      },
      {
        "href" : "http://localhost:7878/question/3/answer",
        "method" : "POST",
        "message" : "게시글에 답변을 남깁니다."
      }
    ]
  }
}

게시글을 등록했으니 클라이언트가 할 일을 예측하여 하이퍼링크를 제공합니다.

  1. 지금 등록한 게시글의 상세 내용을 응답한다. (READ)
  2. 지금 등록한 게시글을 삭제한다. (DELETE)
  3. 지금 등록한 게시글에 답변을 남긴다. (CREATE)

이런 방식으로 설계하면 클라이언트가 리소스를 사용하면 연관된 다른 리소스를 파악할 수 있어서 REST API를 더 RESTful 하게 만들 수 있다.

0개의 댓글