Post

좋은 REST API를 설계하는 방법(1): REST API의 6가지 원칙

✏️ Edit
좋은 REST API를 설계하는 방법(1): REST API의 6가지 원칙

우리가 새로운 프로젝트를 진행한다고 생각해봅시다!

프로젝트를 시작하기 전 다양한 방법들에 대한 정의가 필요하겠지만, 원하는 기능에 대해 정리해보는 게 우선이겠죠!

어플리케이션에서 수행되기를 원하는 다양한 기능들에 대해 요구사항으로 정리할 수 있습니다.

요구사항들을 정리하다보면 구현해야할 기능의 목록이 나올거에요. (BDD 개발 방식에서는 이 과정을 Discovery workshop 과정을 통해 진행하기도 합니다.)

구현해야 할 기능 목록에 따라 해당 기능을 직접적으로 수행할 API를 개발하게 되겠죠.

API 아키텍쳐는 다양한 스타일이 존재하지만 그 중 저희는 REST API를 가장 많이 사용하게 됩니다!

이러한 REST API를 잘 설계하는 것이 좋은 어플리케이션 개발의 시작이 됩니다.

REST API를 잘 설계하기 위해 우선 REST API의 원칙에 대해 알아봅시다.

REST API의 6가지 원칙

Image

1. Client - Server

클라이언트는 화면을 보여주고, 화면에 보여줄 데이터가 필요할 경우, 서버에 요청을 보내는 역할만 합니다. 서버(데이터 저장소)는 요청받은 데이터를 원하는 형태에 맞춰 클라이언트에 전달만 하면 됩니다. 즉, 클라이언트와 서버의 역할이 명확히 분리됩니다.

이를 통해 다양한 플랫폼을 사용할 수 있으며(=웹, 모바일 등 어느 화면에서든 같은 서버의 api를 사용할 수 있습니다.), 서버 구성을 간소화하여 확장성을 증대할 수 있습니다.

만약 서버코드와 클라이언트코드가 하나의 코드에서 이루어진다면 이러한 장점을 얻기 힘들 것입니다.

2. 무상태(Stateless)

각 REST API의 요청을 독립적으로 처리되며, 클라이언트는 각 요청에 필요한 모든 정보를 포함하고 있습니다. 세션 정보, 요청 간의 연결 상태 등은 모두 클라이언트에 정보가 있습니다. (서버는 각 요청을 독립적으로 처리할 뿐 다른 요청에 대한 정보는 저장하고 있지 않습니다.)

예를 들어:

  • 클라이언트가 첫 번째 요청으로 서버에 로그인을 했다면, 서버는 이 요청을 처리하고 응답할 때 토큰을 발급할 수 있습니다.
  • 두 번째 요청에서 클라이언트가 데이터를 요청할 때, 클라이언트는 이전에 받은 토큰을 포함해서 서버로 요청을 보내야 합니다. 서버는 이전 요청의 상태를 기억하지 않기 때문에, 클라이언트가 다시 보내는 이 토큰을 기반으로 인증을 확인합니다.

3. 캐시(Cacheable)

네트워크 성능 향상을 위해 캐시 가능 여부를 명시할 수 있습니다. 만약 캐시 가능인 경우, 동일 요청에 대해 캐시 데이터로 재사용할 수 있습니다.

4. 계층화 (Layered System)

클라이언트는 보통 서버 엔드포인트에 직접 연결되지 않습니다. 보통 가운데 중간 서버를 두게 됩니다. 로드밸런싱이나 캐싱을 두어 시스템 확장성을 높입니다.

확장성은 어떻게 높이나요?

  • 로드밸런싱: 클라이언트로부터 요청이 너무 많을 때, 로드 밸런서를 통해 여러 서버로 분산하여 과부하를 방지합니다.

  • 캐싱: 중간 레이어에서 데이터를 캐싱하여 동일한 요청의 경우 서버에 가지 않고 클라이언트로 응답을 줄 수 있습니다. 자주 요청되는 데이터(이미지, 정적 파일 등)에 대해 캐싱처리를 해둘 수 있습니다.

계층화는 보안정책에도 도움을 줄 수 있습니다. 중간 레이어는 방화벽 역할을 하거나, SSL 암호화와 같은 보안 정책을 적용할 수 있습니다.

민감한 데이터 요청이 서버에 도달하기 전에 중간 레이어에서 유효성 검사와 필터링을 수행합니다. 인증 및 권한 부여 로직도 중간 계층에서 처리하여 서버의 부담을 줄일 수 있습니다.

예를 들어 클라이언트가 API 요청을 보낼 때, 중간 계층인 API Gateway에서 OAuth2 인증을 처리합니다. (클라이언트 토큰 유효 확인, 권한 부여 등) 이 때 인증이 완료되면 서버에 보내고, 중간 인증 오류가 발생하면 다시 클라이언트에 보냅니다.

5. 코드 온 디맨드(Code-on-Demand)

REST API 6가지 원칙 중 유일하게 optional한 원칙입니다. 코드 온디맨드는 클라이언트가 실행 가능한 코드(applet, scripts)를 다운로드하여 기능 확장성을 높이는 기능입니다.

하지만 보안, 유지보수 등의 이유로 현재에는 잘 쓰이지 않습니다. REST API의 원칙이 생길 무렵에는 웹이란 대부분 정적 document였고, 웹 클라이언트도 브라우저 그 자체일 뿐 별다른 기능을 수행하기 어려웠었습니다. 그렇기 때문에 클라이언트 단에서 필요한 로직을 구현하기에 충분하지 않았어서 코드 온 디맨드가 필요했을 것으로 추측해볼 수 있겠죠..?

6. 일관된 인터페이스(Uniform Interface)

이 원칙은 시스템을 단순하고, 확장 가능하고, 독립적으로 발전하기 위한 목적을 가집니다. REST API의 인터페이스는 일관되어야 한다! 라는 것입니다.

풀어서 설명하자면, 클라이언트-서버 사이에 데이터를 요청/응답할 때, 동일한 리소스를 사용하고, 요청에 대한 동작은 통일해야 합니다.

리소스는 시스템에서 사용되는 모든 데이터를 의미합니다. 예를 들어 고객, 주문, 상품 등이 됩니다.

요청에 대한 동작은 GET, PUT, POST, DELETE 등의 메서드를 의미합니다.

제가 만약 고객의 주문 정보에 대해 알아본다고 해봅시다.

이떼 저는 user과 order라는 두 리소스를 사용합니다. 만약 user 대신 customer, order 대신 purchase 등의 용어를 사용하면 안됩니다. 리소스를 명확히 구분해두고, 정해둔 용어를 사용해야 합니다.

  • GET /users/123/orders/13(O)
  • GET /customers/123/purchase/13 (X)

이것이 일관된 인터페이스를 위한 첫번째 조건 리소스 식별(Identification of Resources) 입니다.

제가 고객을 조회하고, 삭제하고 싶습니다.

이때 저는 GET /users/123, DELETE /users/123 이와 같이 리소스 표현을 통해 내부 구현에 대해 알 필요 없이 사용할 수 있습니다. 이것이 리소스 표현을 통한 조작(Manipulation of Resources Through Representations) 입니다.

클라이언트의 요청과 서버의 응답에는 메시지를 처리하는 데 필요한 모든 정보를 포함해야 합니다.

  • 요청 예:

    1
    2
    3
    
    GET /users/123 HTTP/1.1
    Host: example.com
    Content-Type: application/json
    
    • GET: 작업의 종류(읽기 요청).
    • /users/123: 요청 대상 리소스.
    • Content-Type: 데이터를 어떤 형식으로 처리해야 하는지 명시.
  • 응답 예:

    1
    2
    3
    4
    5
    6
    
    HTTP/1.1 200 OK
    Content-Type: application/json
    {
      "id": 123,
      "name": "John Doe"
    }
    

이것이 자가 설명 메시지(Self-Descriptive Messages)입니다. 이를 통해 클라이언트는 별도의 매뉴얼 없이도 메시지를 읽고, 어떤 작업을 수행해야 할지 바로 알 수 있습니다.

다음으로 HATEOAS(Hypermedia as the Engine of Application State) 원칙도 있으나 현대에는 많이 사용되지 않습니다. HATEOAS는 하이퍼미디어를 사용해 클라이언트가 다음 작업을 할 수 있는 경로를 제공합니다. 예를 들어 고객의 주문건을 조회하고 싶다면 고객 조회 api 응답에 아래 구조와 같이 다음에 조회할 api uri를 추가해주는 것입니다.

1
2
3
4
"links": {
    "self": "/users/123",
    "orders": "/users/123/orders"
  }

그렇지만 응답에 link를 표현시키기에 구현이 복잡하고, 이를 다시 클라이언트에서 처리하기에 클라이언트 지원이 부족할 수 있습니다.

대안으로 최근에 주로 사용되는 방법입니다.

  • Swagger (또는 OpenAPI): REST API 스펙의 문서화 도구. 정적 문서화를 통해, 클라이언트에서는 문서를 참고하여 필요한 경로와 요청 방법에 대해 알 수 있습니다.(사전 정의된 문서로 모든 정보 획득 가능)
  • GraphQL: API 쿼리 언어이자 런타임 환경입니다. 클라이언트 중심의 데이터 요청이기에 클라이언트에서 필요한 데이터만 요청할 수 있습니다.

즉, 단순하고 직관적인 시스템을 만드는 것이 RESTful API의 목표입니다.

  • 리소스는 URI로 식별되고, 표현을 통해 조작됩니다.

  • 자가 설명 메시지HATEOAS로 클라이언트는 별도의 지식 없이 필요한 작업을 수행할 수 있습니다.

참고: https://medium.com/bytebytego-system-design-alliance/best-practice-and-cheat-sheet-for-rest-api-design-6a6e12dfa89f