Jira의 REST API 문서를 읽고 얻은 인사이트를 공유합니다
유저가 소유한 리소스 - /users/me/resources
vs /resources
유저가 소유한 리소스를 나타내는 REST API URI를 설계할 때 원칙에 따른다면
/resources
: 현재 로그인한 유저와 관계 없는 전체 리소스/users/me/resources
: 현재 로그인한 유저가 소유한 리소스
이렇게 명명을 해야할 것 같다.
그러나 현실적으로 Jira 처럼 로그인해서 쓰는 애플리케이션은 내 데이터만 볼 수 있는 경우가 많고, 그렇다면 대부분의 api가 /users/me
하위에 위치하게 되어 리소스 분류가 안 되어 좋은 설계는 아닌 것 같다는 생각이 들었다. 그래서 Jira는 어떻게 하는 지 찾아 봤다.
예시: /rest/api/3/dashboard
이 API는 (로그인한) 유저가 소유하거나 공유받은 대시보드 리스트를 반환한다. 그러나 URI 어디에도 /users/me
처럼 내 것이라는 것을 명시적으로 표현하지는 않는다. Jira 애플리케이션의 요구사항 중에서 모든 유저가 소유한 대시보드를 조회하는 기능은 구현할 필요가 없기 때문에 구분할 필요가 없고 명시적으로 드러내지 않은 것으로 추정된다. (아마 그런 api가 있다면 /admin
하위 리소스일 것이다)
이를 통해 현재 로그인한 유저가 소유한 리소스는 /users/me
로 반드시 드러낼 필요는 없는 것을 알았다. 요구사항을 고려해서 모든 리소스 조회, 유저가 소유한 리소스 조회 두 가지를 모두 구현해야 하지 않는다면 생략해도 좋을 것 같다.
일부 필드 필요할 때만 호출하기 - expand
query parameter
프로젝트를 진행하다 보면 API response에 엔티티와 직접적으로 연관되지는 않은 필드를 포함해야 해서 난감할 때가 있다. 예를 들면, 특정 페이지에서만 필요한 count
나 status
같은 값이다. 추가하는 건 어렵지 않을 수 있지만 문제는 필드를 추가할 때마다 단순 DB SELECT
로 끝나지 않고 엔티티에 포함되지 않는 값을 계산하거나, JOIN을 수행해서 부하가 증가할 수도 있기 때문이다. 모든 필드가 클라이언트가 활용하는 모든 지점에서 필요하다면 불가피할 수 있겠지만, 그렇지 않고 90%는 DB 엔티티만 필요한데 10%의 페이지 때문에 모든 페이지에서 부하가 큰 작업을 수행한다면 비효율적일 것이다. 특히나 그렇게 만든 만능 API를 프론트 개발자님께서 생산성을 위해 여기저기 재사용하고 서비스를 런칭해서 수많은 유저가 들어오는데 마침 그 API가 트랜잭션 설계 잘못 되어 있다거나 SQL 최적화가 안 되어 있다거나 하면 (일을 많이 하는 api면 그럴 가능성이 높다) DB 락이 걸리고 CPU와 램 사용량이 급증하고 API의 response time이 급증하고 유저들은 앱이 느리다고 별점 테러를 하는 성능 대참사가 일어날 수도 있다.
이것을 방지하기 위해 GraphQL에서는 필요한 필드만 불러 Overfetch를 방지하는 것을 중요한 철학으로 두고 있다. 물론 이 장점을 살리려면 부하가 큰 필드는 호출할 때만 값을 계산하고, 프론트 개발자는 그 필드를 필요할 때만 부르는 구현이 필요하기는 하다.
지금 하는 프로젝트에서는 관계 필드는 related
라는 query param을 둬서 호출할 때만 JOIN
하도록 GraphQL에서 설계했다. 그런데 status
나 count
처럼 항상 쓰지도 않고 직접적인 연관이 없으며 추가적인 연산을 요구하는 필드를 추가할 때는 api를 새로 파야 하나 고민을 했었다. 그러던 와중에 Jira 예제에서 expand
parameter를 활용한 사례를 보고 해결이 되었다.
예시: /rest/api/3/issue/{issueIdOrKey}
Expansion param 설명
https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-get
GraphQL과 유사한 철학을 REST API로 구현하기 위해서 Jira는 expand
라는 query parameter를 활용한다. 일부 필드는 이 파라미터에 이름이 입력되었을 때만 값을 반환하는 것이다. 이 방법을 활용하면 일부 페이지에서만 쓰는 별도의 연산이 들어가는 필드를 부담 없이 포함할 수 있을 것이다.
그러나, 아까 GraphQL의 사례에서 언급했던 것처럼 이 장점을 살리려면 구현을 할 때 expand
param에 특정 값이 있을 때만 연산을 수행하도록 구현해야 한다. 당연히 다 계산해놓고 return만 안 한다고 해서 성능 최적화가 되지는 않는다. if
문이나 switch
문으로 특정 QueryDSL 연산을 감싸서 조건부로 특정 값을 불러오도록 설계하자. 또한 프론트 개발자도 특정 필드가 부하가 추가로 드는 것을 알고 필요한 페이지에서만 호출해야 한다. 이를 위해서 백엔드 개발자는 문서에 굵은 글씨로 "필요할 때만 expand param을 추가하세요. 부하가 증가됩니다." 라는 문구를 추가하는 것이 좋을 것이다.
'Essay > 기술 회고' 카테고리의 다른 글
FCM을 이용해서 알림 기능을 구현할 때 고려해야 하는 이슈 (0) | 2024.06.21 |
---|---|
[Frontend] Apollo 캐시값 갱신 - Troubleshooting 기록 (0) | 2022.01.14 |
[Frontend] Troubleshooting 기록 - 회원가입 로직 (0) | 2022.01.03 |
[React] Apollo client useQuery 요청이 무한 반복됐던 이유 (0) | 2021.08.10 |