서론
이전에는 무한 스크롤에 대해서 weeth에서만 구현되어있는 형태만 봤었고, 직접 구현해본 경험은 없었다. 이번 프로젝트를 통해 처음 무한 스크롤을 적용하면서 백엔드 페이징 방식 선택, API 요청 최적화 등등 의 개념과, 무한 스크롤으로 구현을 해야하는 이유, 무한 스크롤의 개념 등을 세세하게 공부했었다
먼저 무한 스크롤을 구현해야하는 이유는 무엇일까?
첫번째로 프론트엔드에서 원활한 데이터 로딩을 도와줄 수 있다. DB 측면에서 대량의 데이터를 제공하는 서비스에서는
대량의 데이터를 한 번에 불러오면 성능 저하가 발생할 수 있고, 사용자 기기의 리소스를 과도하게 사용할 위험이 있다. 따라서 무한 스크롤을 구현하면 초기 로딩 시 필요한 데이터만 가져오고, 이후 사용자의 스크롤에 따라 추가 데이터를 동적으로 불러올 수 있어 메모리 사용량을 최적화하고 로딩 속도를 개선할 수 있다.
무한 스크롤을 구현해야 하는 두번째 이유는 사용자 경험의 향상이다
기존의 페이지네이션 방식에서는 사용자가 특정한 버튼(다음 페이지) 을 눌러야 하지만, 무한 스크롤을 사용하면 스크롤만으로 새로운 데이터를 자동으로 불러올 수 있어 유저 입장에서 더 직관적이고 편리하다. 특히, SNS 피드라던지, 특정 상품을 구매하는 서비스에서는 사용자 몰입도가 중요한 요소인데, 무한 스크롤을 적용하면 사용자가 별다른 조작 없이도 자연스럽게 콘텐츠를 소비할 수 있어 서비스 이용 시간을 증가시키는 데 기여할 수 있다
무한 스크롤은 사용자가 특정 페이지나 버튼을 눌러 데이터를 추가적으로 불러오는 페이징 방식과 달리, 화면을 아래로 스크롤할 때 자동으로 데이터를 불러오는 방식이다. 주로 피드, 상품 리스트, 뉴스 등등 의 데이터를 조회할때 활용되며, 사용자가 별도의 조작 없이 자연스럽게 콘텐츠를 탐색할 수 있도록 한다.
본론
난 수동매칭 리스트 반환과 나의 매칭 리스트 조회 기능을 구현해야 했다. 각각의 기능이 프론트엔드에서 어떻게 활용될지를 뷰를 보면서 고려하며 API 응답 형식을 설계해야 했고, 특히 무한 스크롤을 적용해야 하는 상황이었기 때문에 페이징을 어떻게 처리할 것인지도 중요한 요소로 생각하고 구현했다
수동매칭 리스트
먼저 내가 구현할 내용은 크게 아래 두가지 뷰의 내용이였다

첫번째로는 수동매칭 리스트의 반환이였다. 이 기능은 사용자가 현재 참여 가능한 수동 매칭 방 목록을 불러오는 API를 제공하는 것이였고, 필요한 데이터(DTO)들을 아래와 같이 작성해보며 간추리게 되었다
1. 각 매칭 방을 구별하는 ID
2. 해당 매칭 방의 채팅방 ID
3. 출발지 정보
4. 도착지 정보
5. 출발 예정 시간
6. 현재 참여중인 인원수(방의 최대 인원수는 택시이므로 당연히 4로 고정이였다)
7. 매칭 방과 관련된 태그(비흡연자만, 동성만 등등)
나의 매칭 리스트

두번째로는 나의 매칭 리스트였다. 이 기능은 사용자가 현재 참여 중인 매칭 방을 조회하는 기능이었다.
첫번째의 수동매칭 리스트와 유사한 DTO의 내용이 필요하고 다른점은 추가 설명? 정도 였기에 해당 내용을 확실히 하기위해 작업 전에 프론트분과 소통하였다. 그리고 프론트엔드 요구사항을 확인한 결과, 내가 예상했던대로 수동매칭 리스트와 유사한 형태의 DTO를 반환해줘야 했지만, 추가적으로 사용자가 입력한 멘트(추가 설명)를 포함해야 한다는 점이 달랐기때문에 기존의 MatchingRoomResponse에 description 필드를 추가해줘서 사용자가 작성한 멘트를 전달할 수 있도록 했다.

프론트엔드 팀과 논의한 결과, 응답 형식은 최대한 간소하게 유지하면서도 필요한 정보를 포함해야 했다.
따라서 결론적으로 아래의 구조로 설계하는 것을 생각하며 구현을 시작했다
MatchingRoomResponse 생성 -> List<MatchingRoomResponse> -> MatchingRoomListResponse DTO
-> Slice<T> 페이징 적용 -> API 응답에 포함
MatchingRoomResponse와 별도로 MatchingPageableResponse 또한 생성해줘서, 무한 스크롤을 적용하기 위해 페이지네이션 정보 또한 함께 반환해줄 수 있도록 했다
구현사항
아래는 구현사항 중 일부의 코드의 내용들을 첨부해서, 실제 구현한 로직을 설명해보겠다
나의 매칭방 리스트 조회
나의 매칭방 리스트 조회 서비스단 코드이다.
/*
나의 매칭방 리스트 조회
*/
@Transactional
public Page<MatchingRoomResponse> getMyMatchingList(Long userId, int pageNumber, int pageSize) {
if (pageNumber < 0) {
throw new PageNotFoundException();
}
Members user = memberService.findById(userId);
Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "id"));
Page<MatchingRoom> rooms = matchingRoomRepository.findByMemberInMatchingRoom(user, pageable);
return rooms.map(MatchingRoomResponse::from);
}
먼저, pageNumber가 0보다 작은 경우 잘못된 페이지 요청을 방지하기 위해 PageNotFoundException을 발생시킨다. 이후 memberService.findById(userId)를 통해 현재 사용자의 정보를 조회한 뒤, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "id"))를 이용해 페이지네이션과 정렬 기준을 설정한다. 여기서 정렬 기준은 최신 매칭방 순서를 유지하기 위해 id를 기준으로 내림차순(DESC) 정렬하도록 설정되었다.
그다음, matchingRoomRepository.findByMemberInMatchingRoom(user, pageable)을 호출하여 현재 사용자가 포함된 매칭 방 목록을 가져온다. findByMemberInMatchingRoom 메서드에는 matchingRoomStatus ACTIVE 상태인 방, 그리고 paymentStatus가 LEFT가 아닌 방만 조회할 수 있도록 필터링 해주었다. 필터링 조회 후 페이징이 적용된 결과가 반환되며, 마지막으로rooms.map(MatchingRoomResponse::from)을 통해 MatchingRoom 엔티티를 MatchingRoomResponse DTO로 변환 해줬다
MatchingPageableResponse
@Builder
public record MatchingPageableResponse(
int pageNumber,
int pageSize,
int numberOfElements,
boolean last
) {
public static MatchingPageableResponse of(Slice<?> slice) {
return MatchingPageableResponse.builder()
.pageNumber(slice.getNumber())
.pageSize(slice.getSize())
.numberOfElements(slice.getNumberOfElements())
.last(slice.isLast())
.build();
}
}
MatchingPageableResponse에서는
현재 페이지 번호, 한 번에 가져오는 데이터 개수, 현재 페이지에 포함된 데이터 개수를 반환해줘서
무한 스크롤을 지원하는 페이징 응답 DTO 를 구현했고, last로 마지막 페이지 여부를 반환해줘서 해당 필드를 통해 클라이언트는 추가 데이터를 요청할지 여부를 판단할 수 있으며, 프론트엔드에서 더 이상 데이터를 요청하지 않아도 되는 시점을 쉽게 결정할 수 있도록 하였다
해당 내용으로 무한 스크롤 방식의 데이터 로딩이 원활하게 이루어질 수 있도록 설계했다
구현된 내용
아래는 내가 작성한 API 명세서의 사진이다

느낀점
이번 프로젝트를 진행하면서 무한 스크롤을 처음 구현해보며 많은 것을 배울 수 있었다. 처음 구현해보는 내용이니 만큼 공부도 많이하고 걱정도 많았지만, 테스트때와 실제 QA시 잘 작동하는 것을 보고 뿌듯함을 느낄 수 있었다 👍🏻
이번 경험을 통해서 어떤 API를 개발을 하든 필요한 DTO가 무엇일까에 대한 내용을 미리 고민하고, 프론트와 백엔드 간 데이터 흐름을 위해 세세하게 소통하는 것이 개발 속도랑 퀄리티를 높일 수 있다는 내용도 배울 수 있었다. 해당 가치택시 프로젝트는 Leets에서는 최종 발표 후 마무리 되었지만, 꾸준히 리팩토링을 하며 매칭 서비스와 관련한 추가적인 내용들을 더 구현해보고 싶다는 생각이 있다 🚖 (저희 계속 유지보수 하는거죠...?)