본문 바로가기
가치관 쌓기/개발 돌아보기

REST API 에서 URL은 무슨 의미인지 살짝 맛 본 썰!!

by simplify-len 2022. 5. 19.

이번에 멤버십 환불/해지 고도화 프로젝트를 하면서 API 에 대한 피드백이 있었습니다.

아래 "/v1/membership" 에 대한 API 는 무엇을 의미하는 걸까요? 잠시 숨을 고르고 천천히 코드를 살펴봅니다.

@GetMapping(value = {"/v1/membership"})
public String member(@RequestParam(value = "clubId") String clubId,
                     @RequestParam(value = "userId") String userId,
                     @RequestParam(value = "state") String state) {
    Member member = membershipGetUseCase.member(UserId.of(userId), ClubId.of(clubId), MemberState.valueOf(state));
    return String.valueOf(member.getId().getId());
}

.

.

.

.

.

.

.

.

.

.

.

.

.

.

위 API는 특정 멤버쉽을 조회합니다. 이 때 clubId 와 userId 를 기준으로 state가 어떤 상태인지를 필터링 하여 조회합니다.

"/v1/membership" 가 문제가 있어보일까요? 사실 그렇지 않아요. 하지만 문제가 있어요. 어떤 문제일까요?

개발이 어느정도 되고나서 코드를 살펴보는 도중 해당 API에서 state 라는 단어에 어색함을 느꼈고, 느낀 이유는 명확했습니다. 이유는 '사용되는 클라이언트에서는 특정 state의 멤버십을 조회하는 곳이 아직까지는 단 한 곳 밖에 없기 때문입니다. 사용하는 측에서는 state를 하드코딩해서 무조건 사용할 것입니다.' 그러므로, state 를 제거하는 게 맞다. 라고 생각했습니다.

처음 이 API를 만든 개발자님은 범용성을 고려하고 만들었다고 하지만, 범용성이라는 단어가 적용되기 위해서는 사용되어지는 클라이언트가 많아야 가능한데, 그렇지 않았기 때문에 위 API 는 적합하지 않다고 판단했습니다.

그래서 고려했던 다음 URL은 아래와 같았습니다.

제가 의도한 API는 'clubId와 userId를 기준으로 멤버십을 해지시킬 수 있는 멤버십을 조회한다.' 입니다. 그렇게 작성한 API는 아래와 같은데요.

@GetMapping(value = {"/v1/membership/terminate"})
public String member(@RequestParam(value = "clubId") String clubId,
                     @RequestParam(value = "userId") String userId) {
    Member member = membershipGetUseCase.member(UserId.of(userId), ClubId.of(clubId));
    return String.valueOf(member.getId().getId());
}

이 또한 좋지 못한 API 입니다. 왜일까요?

'clubId와 userId를 기준으로 멤버십을 해지시킬 수 있는 멤버십을 조회한다.' 라는 것을 과연 "/v1/membership/terminate" 을 보고 판단할 수 있을까요? 그렇지 않을 것 같아요.

"/v1/membership/terminate" 를 보고 누구나 예측가능해야 하나, 해당 API는 해지된 멤버십을 조회할 것만 같아요. 그러므로 적합한 URL은 아닌것 같습니다.

더 적합한 URL을 어떻게 만들 수 있을까?

@GetMapping(value = {"/v1/members/{state}"}) // -- ex. 1
@GetMapping(value = {"/v1/current/members"}) // -- ex. 2
@GetMapping(value = {"/v1/members/joined"}) // -- ex. 3
@GetMapping(value = {"/members"} params = {
                VERSION + IS + V_0_1_0,
                FUNCTION + IS + FIND}) // -- ex. 4
public String member(@RequestParam(value = "clubId") String clubId,
                     @RequestParam(value = "userId") String userId) {
    MemberId memberId = queryMembership.findTerminableMember(UserId.of(userId), ClubId.of(clubId));
    return String.valueOf(memberId.get());
}

약 4가지의 URL 이 나왔었고, 위 4가지 중 3번째를 선택했습니다. 결론부터 이야기하면 1,2,3,4 모두가 약간의 취향을 타는 것으로 판단되고 그 중 3번째 URL이 최종적인 형태가 되었습니다.

특이했던 부분은 4번째 "/members?v=0.1.0&f=find&clubId=x&userId=x" 이 부분인데요. 'f=find' 부분을 원하는 function으로 변경할 수 있어 유연성을 가질 수 있습니다.

단 하나의 API이지만, 끊임없이 고민해야 되는 것이 바로 API 인것 같아요.

트레바리 CTO 님은 API URL 의 대한 비유를 파일 디렉토리로 말씀하셨습니다.

image-20220519210634042

/work/memberships/1234/members/윤태진

위 URL 을 왼쪽에서 오른쪽으로 읽어가면서 리소스를 찾아갑니다. 우리가 보는 대부분의 URL 은 왼쪽에서 오른쪽으로 읽어가면서 어떤 리소스를 가져올 것인가를 표현될 수 있어야 합니다.

우리가 만드는 REST API 이러한 형태를 잘 가지고 있을까?

댓글