4. API 설계하기(1) - API에 대해

bruce1115·2023년 11월 18일
0

EduMax

목록 보기
5/12

좋은 API는 어떤 API일까?

API를 설계하려고 하다 보니 가장 먼저 든 의문은 그런데 어떻게 짜야 좋은 API를 만들 수 있을까? 였다. 내 첫 프로젝트에서 API 주소를 만들 때는 솔직히 말해서 깊은 고민을 별로 하지 않았다. 남에게 공개할 목적으로 만들어진 Open API가 아니어서 그런 것도 있었던 것 같고, REST API라는 개념만 어렴풋하게 알고 있어서 DB에 데이터 저장할 목적이 아니면 GET, 새롭게 저장하는건 POST, 삭제는 DELETE, 수정은 PATCH를 쓴다는 정도로 이해하고 넘겨줘야 할 거 같은 데이터는 일단 다 JSON 안에 눌러 담았다.
그 때 짰던 코드를 보면 절대 좋은 코드라고 말할 수 없었다. 요청이 성공하는 경우에만 처리하고, 실패한 경우의 에러 처리를 단순히 Typescript의 throw error를 이용해서 모든 에러를 뭉뚱그려서 했다. 나중에 Frontend와 연결할 때 AxiosError가 발생했는데 그게 어떤 에러인지 정확히 몰라서 일일이 추적하느라 고생을 많이 했었다. 그리고 쓰일 것 같은 데이터를 모조리 전달했었는데 실제로는 쓰이지 않는 데이터까지 전달하는 등의 문제가 있었다. 이러한 문제들을 겪고 이번 프로젝트에서는 좋은 API란 어떤 것일까에 대한 고민을 API를 실제로 설계하기 전 반드시 해야겠다고 생각했다.

내가 생각했을 때 좋은 API는 곧 사용자가 사용하기 쉬운 API이다. 어떻게 짜야 사용자 입장에서 사용하기 편할까를 구글링하면서 생각해 본 결과 아래의 몇 가지 조건들을 생각해 볼 수 있었다.

  • 불필요한 정보를 추가하는 것은 최대한 삼가야 한다. 그리고 어떤 field를 제공할 지는 신중하게 선택해야 한다. field가 부족하여 새 데이터를 추가하는 것은 어렵지 않지만, 이미 존재하는 field를 삭제하거나 수정하면 API를 이용하는 프로그램에서 에러가 생길 가능성이 있다. 따라서 무조건 필요하다고 생각되는 데이터만 처음에 field에 넣고, 확실하지 않은 field는 추가하지 않아야 한다.

  • 에러를 확실하게 표시해 주는 것이 좋다. 가장 적절한 방법은 적절한 error code를 보내 주는 것이다. 예를 들면 다음과 같은 코드들을 쓸 수 있을 것이다.

    • 400 Bad Request - 클라이언트가 유효하지 않은 요청을 보낸 경우 (request body나 파라미터를 빼먹은 경우같이)
    • 401 Unauthorized - 해당 서버에 클라이언트 인증이 실패한 경우
    • 403 Forbidden - 클라이언트가 인증은 됐지만 요청한 자원에 대한 권한은 없는 경우 (예를 들어 로그인된 사용자가 관리자 페이지에 접근하는 경우 겠죠?)
    • 404 Not Found - 요청한 자원이 존재하지 않는 경우
    • 412 Precondition Failed - Request Header 필드 중 한 개 이상의 값이 잘못 된 경우
    • 500 Internal Server Error - 서버에서 발생된 일반적인 에러
    • 503 Service Unavailable - 요청된 서비스가 이용가능하지 않는 경우

    요점은 에러가 났을 때 무슨 일이 일어났는지 확실히 알려 주는 것이 중요하다는 것이다.

  • 값을 사용자가 어느 정도 예측 가능해야 한다. 예를 들어 응답이 정해져 있는 경우, ENUM type을 사용하여 값을 전달한다. 그리고 Boolean 값에 null을 넣지 않는 등 사용자가 예상 가능한 범주에서 값을 보내 주는 것이 좋은 것 같다. field 이름의 형식을 최대한 맞추는 것도 이에 해당될 수 있을 것 같다.

  • 시스템이 제대로 작동하는지 확인할 수 있는 방법도 만들어야 한다. 서버 상태가 정상이 아닌데 계속 요청을 보내는 일을 막기 위해, 현재 API 응답이 제대로 이루어지고 있는지 확인할 수 있어야 혼란을 방지할 수 있다.

REST API가 뭐지?

API 설계에 대해 검색하며 가장 많이 나온 말이 REST API이다. 사실 진짜 대강 알고 있는 개념이고, 이를 정확하게 설명하라고 하면 설명하는 것이 쉽지 않았다.
REST란 REpresentational State Transfer 의 약자이다. 즉 표현에 의해 State를 전달한다는 뜻이고, 표현은 자원의 표현을 의미한다. 즉 URI에는 자원, Resource를 쓰고 Method를 통해 CRUD를 적용하는 방식이라고 이해하면 될 것 같다. REST에는 6가지 제약 조건이 존재한다고 한다.

1. client-server

간단히 말해 클라이언트와 서버가 독립적으로 분리되어 있어야 한다는 뜻이다. 클라이언트와 서버를 독립적으로 구축할 수 있다는 뜻이므로, Backend와 Frontend를 나누어 개발하는 것을 생각하면 될 듯 하다.

2. stateless

클라이언트와 서버가 서로의 상태를 기억할 필요가 없다는 뜻이다. 그러니까 서버는 각 사용자에 대한 어떠한 정보도 기억하지 않는다. 각 요청은 과거의 요청과 상관없이 독립적으로 처리되어야 한다.
이 성질로 인해 서버는 각 클라이언트를 일일이 추적할 필요 없다. 모든 필요한 정보는 전부 request 안에 담겨 있기 때문이다.

3. cache

서버에서 데이터를 클라이언트로 보내 줄 때 반드시 데이터가 캐시 가능한지를 표시해 주어야 한다. http에서는 cache-control header를 이용한다고 한다.

4. uniform interface

앞서 말한 대로, 자원에 대한 요청을 한정적인 method를 통해 수행하는 것과 관련이 있는 성질이다. 네 가지로 나눌 수 있는데, 네 가지 전부를 만족시키는 것이 상당히 힘들기 때문에 완벽한 REST API는 구현하기 힘들다고 한다. 네 가지 세부 사항은 다음과 같다.

  • identification of resource : 자원의 식별이다. URI에 resource 관련 정보가 들어 있으므로 이를 만족시키는 것은 쉽다.
  • manipulation of resources through representations : 표현을 통한 자원의 조작이라는 뜻인데 이 말은 요청에 대한 응답이 JSON이나 XML 같은 표현으로 전달된다는 것이다. 역시 무리 없이 충족된다.
  • self-descriptive messages : 자기 자신을 설명하는 메시지여야 한다. 이를 만족시키려면 API를 설명한 문서가 응답 자체에 존재해야 한다. 여기서부터 우리가 일반적으로 사용하는 API가 만족하지 못하는 조건이라고 할 수 있다.
  • HATEOAS : hypermedia as the engine of application state, 즉 하이퍼링크를 이용한 상태의 전이를 가능하게 해야 한다. 이 말은 취할 수 있는 동작, 즉 해당 URI에서 클라이언트가 다음 동작으로 취할 수 있는 모든 경우의 수를 hyperlink를 이용하여 알려 주어야 한다. Hypertext Application Language를 활용하여 링크를 추가하면 달성할 수 있다.

이 외에도 layerd system(계층화), code-on-demand(서버에 코드를 요청하고, 서버는 실행가능한 코드를 제공) 원칙이 있어 총 여섯 가지가 있다. 그 중 code-on-demand는 optional이라고 한다.

결론

REST API에 대해 자세히 알아보면서 내가 지금 만드려고 하는 API는 엄밀히 따지자면 uniform interface의 self-descriptive, HATEOAS 조건을 만족하지 않으므로 REST API라고 할 수 없다는 것을 알게 되었다. 위 조건을 굳이 만족시킬 이유를 아무리 생각해도 찾지 못해서 프로젝트에는 나머지 조건만 만족하는 HTTP API를 쓰기로 하였다. 그리고 representation은 Frontend를 React.js를 이용하여 구현할 것이기 때문에 JSON이 가장 적합하다고 결론내렸다.

다음 글에서는 이제 HTTP API의 URI와 Method를 정의하고, 각 request별 response, request body를 어떻게 해야 할지 JSON으로 정의해 볼 생각이다. 양이 많아질 것 같아서 두 편에 걸쳐 글을 쓸 수도 있을 것 같다.

profile
백엔드 개발자 꿈나무

0개의 댓글