Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api: ✨ 카테고리에 등록된 소비 리스트 무한 스크롤 조회 API #120

Merged
merged 51 commits into from
Jul 3, 2024

Conversation

psychology50
Copy link
Member

작업 이유

  • 카테고리에 등록된 소비 내역 리스트 개수 조회 API
  • 카테고리에 등록된 소비 내역 무한 스크롤 조회 API

둘을 따로 분리한 이유는 무한 스크롤 할 때마다 전체 개수를 체크하는 쿼리를 쏘는 게 너무 낭비라고 생각했기 때문입니다.


작업 사항

혹시 무한 스크롤 응답에 대해 잘 모르신다면, 꼭 질문해주세요! 페이지네이션과 살짝 다릅니다.
내용이 어려우실 거 같아서 Sequence Diagram으로 그렸다가, 오히려 더 복잡한 거 같아서 부분 별로 쪼개봤습니다.
참고로 전체 개수를 조회하는 API는 별도로 설명하지 않습니다. (로직이 똑같음.)
DB에 전송하는 쿼리만 마지막에 작성해두겠습니다.

1️⃣ Controller 계층

image

  • 요청 경로: GET /v2/spending-categories/{category_id}/spendings
    • category_id는 type 쿼리의 종류에 따라 pk로 쓰일 수도, icon 번호로 쓰일 수도 있습니다.
  • 쿼리 파라미터
    이름 설명 기본값 필수
    type "default"면 시스템 정의 카테고리, "custom"이면 사용자 정의 카테고리 -
    size 한 번에 몇 개의 데이터를 가져올지? 30
    page 몇 번째 페이지를 가져올지? 0
    sort 정렬 기준 소비 날짜
    direction 정렬 방향 역방향(최신순)
    • type 필드의 기본값으로 "default"를 넣으려다가, 카테고리 pk가 1~11인 데이터를 소유하고 있는 사용자의 경우 잘못된 응답을 받을 수도 있지 않을까 싶어서 막아버렸습니다. 근데 이건 너무 강박증 같긴 하네요.
  • 예외 처리
    • 자원 접근 검사: type이 custom이면, category_id 데이터에 접근 권한이 있는 지 manager가 확인합니다. (default면 bypass)
    • type, category_id 조합 검사: type이 default이면서, category_id가 0(CUSTOM) 또는 12(OTHER)인 경우 400 에러

2️⃣ UseCase 계층

image

  • UseCase 내부에선 Service 응답값으로 Slice된 결과를 받습니다.
  • 지출 내역은 다양한 년/월/일을 가질 수 있으므로, 기존의 SpendingSearchRes.Month DTO를 배열로 갖는 신규 DTO를 추가해주었습니다.
    • 무한 스크롤이므로 페이지 정보 추가 (필드명은 iOS팀의 요구 사항에 맞춤)
    • 모든 데이터는 년-월-일을 순서로 역정렬 (모두 같다면, spending.id를 기준으로 정렬)

3️⃣ Service 계층

image

  • SearchService 내부에선 type에 따라 같은 도메인 서비스의 다른 메서드를 호출합니다.
    • type이 custom이면, readSpendingsSliceByCategoryId() 호출 : spending_custom_category 테이블을 조인함.
    • type이 default면, readSpendingsSliceByCategory() 호출
  • 사실 Domain Service 내부에서 분기 처리를 할 수도 있긴 합니다. 그 쪽이 더 깔끔할 수도 있긴 한데, 해봐야 알 것 같아요.

4️⃣ DAO 계층

image

  • 위 쿼리는 실제 호출된 쿼리를 기반으로 작성했습니다.

➕ 전체 개수 조회 시 DB에 전송하는 쿼리

-- 시스템에서 제공하는 카테고리인 경우
SELECT COUNT(s.id) 
FROM spending s 
LEFT JOIN USER u ON u.id=s.user_id AND (u.deleted_at IS NULL) 
WHERE (s.deleted_at IS NULL) AND u.id=? AND s.category=?;

-- 사용자 정의 카테고리인 경우
SELECT COUNT(s.id) 
FROM spending s
LEFT JOIN user  u ON u.id=s.user_id AND (u.deleted_at IS NULL) 
LEFT JOIN spending_custom_category scc ON scc.id=s.spending_custom_category_id and (scc.deleted_at IS NULL) 
WHERE (s.deleted_at IS NULL) AND u.id=? and scc.id=?;

리뷰어가 중점적으로 확인해야 하는 부분

  1. 월 별 총합 필요 없는데 왜 응답값으로 내보내는지?
    • 기존 코드 재활용하다보니..이게 싫으면 새로 만들거나, 기존 로직을 수정해야 함. (지금 생각해보면 총합을 Mapper에서 구해주는 게 좀 이상)
  2. Mapper의 toMonthSlice() 메서드 시간 복잡도 대략 O(KlogK) 정도로 고려됨.
    • size 크기가 해봐야 30 정도라 딱히 문제될 거 같진 않은데, 어떻게 생각하시나요..?
    • 그보다 코드 너무 복잡할 거 같아서, 최대한 정리해두긴 했는데 너무 어렵거나 다른 대안 있으면 말씀해주세요!
  3. Pageable의 size 상한선을 제한하지 않았는데, 필요하다고 보시나요?
    • 현재는 클라이언트가 ?size=100000라고 써버리면, 그대로 통과합니다. 🤗
    • 만약 제한한다면 어느 정도로 해보는 게 좋을까요~

중점적으로 확인할 부분이라기엔 좀 애매한데, 테스트 케이스를 작성하지 않은 이유는 생각보다 구현이 너무 오래 걸려서..
여기서 테케까지 작성해보려고 욕심 부렸으면, 시간이 배로 걸렸을 거 같아 일단 포기하고 빠르게 넘겨버렸습니다.
혹시 필요하다고 생각하시면 추가해둘게용.


발견한 이슈

  • 없음

@psychology50 psychology50 added the enhancement New feature or request label Jul 2, 2024
@psychology50 psychology50 self-assigned this Jul 2, 2024
Copy link
Member

@asn6878 asn6878 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다! 아래 코멘트 하나만 확인해주시면 될 것 같습니다ㅎ

혹시 무한 스크롤 응답에 대해 잘 모르신다면, 꼭 질문해주세요! 페이지네이션과 살짝 다릅니다.

무한 스크롤을 구현하는 것은 해본적이 없어서, Slice에 대해서 조금 공부했습니다.
궁금한건, 클라이언트에서는 hasNext 필드값을 이용해 다음 Api 요청을 할지 말지를 결정하는 것인가요?

Mapper의 toMonthSlice() 메서드 시간 복잡도 대략 O(KlogK) 정도로 고려됨.

조회된 데이터를 클라이언트에게 가공해 전달하기 위한 전처리 치고는 무거운 연산량인 것 같긴한데, 저도 현안이 최선책인듯 합니다ㅎ.

Pageable의 size 상한선을 제한하지 않았는데, 필요하다고 보시나요?

다른 부분들과 다르게 pagenation에 사용되는 limit은 클라이언트단에서 값이 변경될 일이 없는 부분인 것 같아 정상적이지 않은 수준의 요청에 대응을 굳이 할 필요는 없다고 생각합니다.

@psychology50
Copy link
Member Author

무한 스크롤을 구현하는 것은 해본적이 없어서, Slice에 대해서 조금 공부했습니다. 궁금한건, 클라이언트에서는 hasNext 필드값을 이용해 다음 Api 요청을 할지 말지를 결정하는 것인가요?

네, 맞습니다.
Pagenation이 전체 페이지 개수와 현재 페이지, 현재 페이지의 원소 수를 응답으로 보낸다면,
무한 스크롤을 위한 Slice는 전체 개수는 당장 알지 못 하지만, 존재 여부만을 파악하여 hasNext 필드로 알려줍니다.

다음 원소 존재 여부를 알 수 있는 이유는 데이터를 가져올 때 기본 size인 30에서 1을 더한 값만큼 불러오기 때문입니다. .limit(pageable.getPageSize() + 1);
만약 원소수가 31개라면 추가로 불러올 데이터가 있다고 판단할 수 있는 것이고, 응답 시엔 31번 째 데이터는 제외합니다.

조회된 데이터를 클라이언트에게 가공해 전달하기 위한 전처리 치고는 무거운 연산량인 것 같긴한데, 저도 현안이 최선책인듯 합니다ㅎ.

ㅠㅠ 더 나은 방법이 있을 지 고민해볼게요.

다른 부분들과 다르게 pagenation에 사용되는 limit은 클라이언트단에서 값이 변경될 일이 없는 부분인 것 같아 정상적이지 않은 수준의 요청에 대응을 굳이 할 필요는 없다고 생각합니다.

아, 이런 부분들을 신경쓰는 이유는 정상적인 client의 요청이 아닌 악의적인 공격에 대응할 필요가 있는 지에 대해 논의해보기 위함이었습니다!
물론 그런 부분들은 proxy에서 걸러내는 것이 맞긴 하지만, 당장 그런 인프라가 마련되어 있기 때문에 애플리케이션에서 막아주어야 할 지 고민 중입니다~~

Copy link
Member

@asn6878 asn6878 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의견 남겨뒀습니다!
승인할게요ㅎ

@psychology50 psychology50 merged commit cb8888d into dev Jul 3, 2024
1 check passed
@psychology50 psychology50 deleted the feat/PW-399-get-spendings-by-category branch July 3, 2024 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants