Backend

RESTful API 설계의 trade-off

mopil 2024. 2. 17. 02:38
반응형

일반적으로 API를 설계할 땐, REST 원칙을 적용하여 설계하곤 한다.

 

이번 글에서는 내가 생각하는 실무적인 RESTful 한 API와 그 과정에서의 trade-off 몇 가지를 공유하고자 한다.

 

 

생성, 수정, 삭제 API의 응답에 관하여

게시글을 생성, 수정, 삭제하는 API를 설계한다고 해보자.

 

REST 원칙에 의거하면 다음과 엔드포인트는 같이 설계할 수 있을 것이다.

 

POST /boards

PUT /boards/{boardId}

DELETE /boards/{boardId}

 

이때 종종 각 API의 응답 필드에 대한 고민을 할 때가 많다.

 

응답은 생성/수정/삭제된 boardId만 리턴할 것인가? 아니면 내용 전체를 리턴할 것인가?

 

엄격하게 RESTful 하게 설계한다면, 각 API의 응답은 boardId만 리턴하거나 아예 리턴 값이 없어야 하고 내용물 조회는 조회 API를 통해서만 해야 한다.

 

결론적으로 필자는 응답에 변경된 데이터를 같이 내려주는 게 훨씬 좋다고 생각한다.

(이는 프론트에서 당장 이 값들을 사용하지 않더라도 의미가 있다고 생각한다.)

 

 

왜 이렇게 생각했는지 몇 가지 단점을 통해 알아보자.

1. 변경된 내용물의 값이 요청 후 필요할 때, API call을 2번 하게 된다.

만약 수정된 데이터를 프론트에서 다시 사용해야 한다면, 프론트는 수정 API -> 조회 API를 2번 호출해야 한다.

API call은 서버 측에선 적을수록 좋다.

 

2. 통합(휴먼) 테스트했을 때 결과를 직관적으로 알기 어렵다.

일반적이면 단위 테스트를 작성하겠지만, 웬만해서 자신이 개발한 API는 보통 PR 전에 한 번쯤 실행해 본다.

 

수정 API를 실행했을 때, ID만 내려간다고 가정해 보자. 이때 수정할 값들이 잘 변경되었는지 응답만으로는 확인하기 어렵다. (보통 디비를 조회해 봐야 할 것이다.)

 

만약 응답값에 수정된 필드들도 같이 내려갔다면 바로 직관적으로 이를 확인할 수 있다.

 

{
	"boardId": 1
}

// better
{
	"title": "변경된 제목",
   	"body": "변경된 내용"
}

 

 

리소스 식별 ID에 관하여

REST 원칙에 의거, 보통 식별 ID는 리소스 바로 다음에 위치해야 한다.

 

즉, board의 식별자인 boardId는 /boards/{boardId}처럼 위치하여야 한다는 것이다.

 

이것에 관하여는 이견이 없지만, 간혹 식별자 ID 위치를 설계하기 애매한 순간이 몇몇 존재한다.

 

예를 들어, 서비스 이용 제한이라는 도메인에서 공통적으로 사용되는 식별 ID는 userId라고 해보자.

 

이 서버의 prefix는 /restrictions로 시작하는 게 직관상 맞아 보인다.

 

그렇지만 /restrictions/{userId}는 직관적으로 이 숫자가 userId를 의미하는지는 알아보기 어렵다.

(이는 /restrcitions/123123인 경우는 더욱 알아보기 어렵다)

 

사실 완전 RESTful 하게는 /users/{userId}/restrctions 가 더 맞아 보인다.

 

근데 거래제한 말고도 user와 관련된 수 없이 많은 도메인 서버 (MSA)가 있다면, 모두 /users/{userId}를 prefix로 붙여야 하는가? 

uri는 아무래도 긴 것보다 짧은 것이 더 직관적이다.

 

주문, 게시물, 거래제한, 판매, 생산, 소비 등등 수없이 많은 user 관련된 도메인이 있다면 더 고민일 것이다.

이런 경우는 header를 활용해 보자

헤더에 user-id: 1233으로 넘겨준다면, 엔드포인트는 깔끔함을 유지할 수 있다.

 

POST /restrictions (유저 이용 제한 등록)

DELETE /restrctions (유저 이용 제한 해제)

GET /restrctions (유저 이용 제한 조회)

 

POST /orders (유저 주문 생성)

... 등등

 

리소스 식별 ID가 여러 개인 경우

유저 신고를 수리하는 기능이 있다고 해보고, 신고 ID는 reportId가 아닌 

발생일자_reportId라고 해보자 (20240101_122)

 

물론 대부분은 단일 ID로 설계를 하겠지만, 그렇지 못한 경우도 종종 있기 마련이다.

 

이런 경우는 좀 더 메인 ID가 될 법한 것을 엔드포인트에 넣고, 나머지는 쿼리 파람으로 빼는 것도 좋은 방법이다.

 

위 예시로는 /reports/{reportId}?reportDate=20240101

 

아니면 둘 다 쿼리 파람으로 빼는 것도 괜찮다.

 

/reports?reportId=122&reportDate=20240101

 

또는 ID 자체를 그냥 붙여서 설정하는 것도 좋다.

 

/reports/{reportId} -> /reports/20240101_122

 

 

 

API 응답값에 관하여

유저 신고를 어드민이 일괄 검토하는 API를 만들었다고 해보자.

POST /reports/review/all

 

이때 프론트에서는 해당 API의 응답으로 필요한 값이 없다고 해보자.

 

그러면 서버는 어떤 응답을 내려줘야 하는가?

 

단순히 리턴 없이, 200 응답 코드만 내려줄 수도 있을 것이다. (이렇게 해도 아무런 문제 되지 않는다.)

 

근데 여기서 일괄 검토 완료된 신고들의 개수를 응답으로 내려주면 훨씬 직관적으로 API를 활용할 수 있을 것이다.

 

이는 맨 처음 소개한 내용(휴먼 테스트 직관적으로 하기)과 연결된다.

 

단, 너무 많은 불필요한 정보를 내리는 건 지양해야 할 것이다.

 

 


 

어느 분야든 정답과 은탄환은 없는 것 같다.

 

팀원들과 합의하에 가장 합리적인 결과를 도출하고, 그 과정에서 유연한 사고를 잃지 않는 것이 중요한 것 같다.

 

반응형