페이지네이션 일반화

💡 일반화의 필요성
💡 일반화 구현
-
일반화 단계에서는 특정 모듈이 아니라
common
(공통) 모듈에서 작업한다. -
페이지네이션에서 그랬던 것처럼 우선 DTO를 만든다.
-
이 DTO는 모든 유형의 페이지네이션을 커버하는 부모 DTO이다.
-
이전과 달라진 점
- 페이지 기반의 페이지네이션도 지원하기 위해
page
프로퍼티도 추가했다. - 글을 최신순으로 보여주기 위해 내림차순 정렬
where__id__less_than
도 추가했다.
- 페이지 기반의 페이지네이션도 지원하기 위해
-
만들어진 부모 DTO를 상속한다
💡 서비스 로직 작성 1
- DTO를
common
모듈에서 만들었듯, 페이지네이트 함수도 마찬가지다. - 로직에서 챙겨할 부분을 순서대로 정리하면
- 먼저 DTO를 기반으로 파인드 옵션을 구성하여 응답할 데이터를 찾는다.
- 파인드 옵션에는 조건(where)이 들어가고 그에 해당하는 데이터를 얻는다.
- 얻은 데이터를 바탕으로 메타데이터(데이터의 수, 마지막 데이터의 id, 다음 요청에 쓰일 url)를 만든다.
- 그럼 천천히 따라가보자.
- 모든 데이터가 들어올 수 있도록 제네릭을 설정한다. 제네릭
T
에는 각각의 모델 엔티티가 들어오고 이는 BaseModel을 상속받는다.
- 로직을 처리하기 위해 요구되는 정보를 생각해본다. 이를 깡그리 파라미터로 받도록 하자. 일단, DTO는 빼박이다. DTO에 담긴 쿼리를 분석하여 데이터를 찾는다.
- 그리고 데이터를 찾는 데는 리포지토리가 이용되기 때문에 그를 두번째 파라미터로 받는다. 세번째는 추가 옵션을 넣어야 할 때를 대비해
overrideFindOptions
을, 마지막은 nextURL을 만들기 위해path
를 받는다.
- 그럼 데이터를 찾아보자. 리포지토리에
find
를 쓰면 된다는 것을 이미 알고 있다. 가령 이런 식인데, 문제는 하드코딩을 해서는 모든 상황에 유연하게 대처할 수 없다는 거다. DTO에 의해 형식이 제한되긴 하지만, DTO가 받는 값들 내에서도 분기처리 할 지점들이 생기기 마련이다. 가령 오름차순이냐 내림차순이냐 같은 문제가 있다.
- 모든 데이터가 들어올 수 있도록 제네릭을 설정한다. 제네릭
T
에는 각각의 모델 엔티티가 들어오고 이는 BaseModel을 상속받는다.
- 로직을 처리하기 위해 요구되는 정보를 생각해본다. 이를 깡그리 파라미터로 받도록 하자. 일단, DTO는 빼박이다. DTO에 담긴 쿼리를 분석하여 데이터를 찾는다.
- 그리고 데이터를 찾는 데는 리포지토리가 이용되기 때문에 그를 두번째 파라미터로 받는다. 세번째는 추가 옵션을 넣어야 할 때를 대비해
overrideFindOptions
을, 마지막은 nextURL을 만들기 위해path
를 받는다.
- 그럼 데이터를 찾아보자. 리포지토리에
find
를 쓰면 된다는 것을 이미 알고 있다. 가령 이런 식인데, 문제는 하드코딩을 해서는 모든 상황에 유연하게 대처할 수 없다는 거다. DTO에 의해 형식이 제한되긴 하지만, DTO가 받는 값들 내에서도 분기처리 할 지점들이 생기기 마련이다. 가령 오름차순이냐 내림차순이냐 같은 문제가 있다.
- 모든 데이터가 들어올 수 있도록 제네릭을 설정한다. 제네릭
- 이런 경우의 수를 분기처리하는 메써드를 따로 만든다. 케이스는 두 가지다.
where
과order
. 내용이 길어지니 새로운 챕터에서 다시 정리하고 짚어 본다.
💡 서비스 로직 작성 2
-
기존에는 id값을 기준으로 이상인 값만 필터링했다(위 코드의 예시).
-
그런데 필터링 기준이 이하인 값일 수도, 아니 아예 id가 아닌 경우가 올 수 있다.
-
가령 내가 좋아요 누른 포스트만 모아 본다든가, 특정 가격대만 본다든가 등등
-
이런 필터링 기준은 무궁무진하다. 그리고 거기서 일반화의 필요도 생겨난다.
-
무엇을 해야 할지 알았으니 필터링을 유연하게 가져갈 수 있는 로직을 작성한다.
-
typeorm
의FindManyOptions
을 다룬다. -
where로 시작한다면 필터 로직을 적용한다.
-
order로 시작한다면 정렬 로직을 적용한다.
-
필터 로직을 적용했다면 언더바 두개
__
를 기준으로 split 했을 때 3개의 값을 얻어내게 된다(프로퍼티, 키, 유틸리티). -
정렬 로직은 이 경우 항상 2개의 값으로 나뉜다.
-
그래서 3개의 값으로 나뉘면, 즉 where 필터면, 2번째 값에 해당하는 오퍼레이터 함수를 적용하여 필터링에 활용한다.
-
어떤 오퍼레이터를 적용해야 할지는 필터맵퍼를 이용한다. 맵핑을 위해 작성한 내용은 다음과 같다.
-
이제
composeFindOptions
메써드를 만든다. DTO를 받아 각각을 담는 변수where
와order
를 선언한다. 이후 반복문을 돌리며parseFilter
에 의해 맵핑된 내용을 저장하고 반환한다.
-
-
여기까지 하면 데이터를 find하는 로직이 완성됐다. 남은 건 이 데이터를 기반으로 메타데이터를 구성하는 일이다. 이전에 작성했던 것과 거의 같으므로 설명은 생략한다.
💡 Whitelist
- 클라이언트에서 실수로 쿼리를 잘못 작성했을 경우가 있다. 혹은 해커들이 악용하거나
- 그럴 때를 대비해서 DTO에 정의한 값 외의 쿼리가 들어올 때 이를 차단하는 법이 있다.
main.ts
의Validation
에서 whitelist 옵션을 주면 된다.- 그러면 밸리데이터에서 검증하지 않는 모든 프로퍼티를 삭제(거부)한다.
- forbidNonWhiteListed 옵션은 whitelist 옵션과 함께 사용되며, 잘못된 쿼리가 들어왔을 때 에러를 내 문제를 알려준다.
Comment ?
▾ Comment