Open yunyechan9893 opened 1 month ago
예찬님
data = data.subList(0, pageSize);
이 부분이 데이터 사이즈를 pageSize에 맞게 -1 해주는 부분입니다
따라서 ProcessedDataCursorResponse<T, U>는 필요 없을 것 같습니다
순원님 data를 -1 해준 후 별도로 data를 가공하고 샆을때땐 어떻게 하나요??
===============tmi return으로 해당 클래스를 반환해줌으로써 data를 -1 해준 후 별도의 데이터 가공을 해줄 수 없는 문제가 있습니다!
그래서 U와 Function을 도입함으로써 이 문제를 해결했습니다
순원님 data를 -1 해준 후 별도로 data를 가공하고 샆을때땐 어떻게 하나요??
===============tmi return으로 해당 클래스를 반환해줌으로써 data를 -1 해준 후 별도의 데이터 가공을 해줄 수 없는 문제가 있습니다!
그래서 U와 Function을 도입함으로써 이 문제를 해결했습니다
쿼리 반환 데이터가 사이즈가 11이라고 한다면 이 11사이즈의 데이터를 가공해주고 마지막 CursorReponse가 될 때 데이터를 -1해줘야 한다고 생각합니다.
저흰 10사이즈의 데이터만 쓸 건데 11사이즈의 데이터 가공은 불필요하지만, 1 사이즈의 데이터 처리로 인한 성능은 아주 미세할 것 같습니다
데이터가
List
class Animal {
List<~> list;
int count;
}
인 경우도 생각해야해요
즉
@Getter
@RequiredArgsConstructor
public class CursorPageResponse<T> {
private final List<T> data; // 이 데이터가 꼭 List가 될 것이라는 보장이 없음
private final Long nextCursor;
private final Boolean hasNext;
public static <T> CursorPageResponse<T> of(List<T> data, int pageSize, ToLongFunction<T> idExtractor) {
boolean hasNext = data.size() > pageSize;
Long nextCursor = -1L;
if (hasNext) {
T lastReponse = data.get(pageSize - 1);
nextCursor = idExtractor.applyAsLong(lastReponse);
data = data.subList(0, pageSize);
}
return new CursorPageResponse<>(data, nextCursor, hasNext);
}
}
인 경우 Animal을 data 인자값으로 넣을 수 없습니다
카톡으로 질문주신 내용 여기에 남깁니다
주말에 나눴던 얘기 중 1번 방식은 현재 진행되고 있는 방식입니다.
@Getter
@RequiredArgsConstructor
@AllArgsConstructor
public class CustomPage<T> {
private T content;
private Long nextCursor;
private Boolean hasNext;
}
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BoardCustomPage<T> extends CustomPage<T> {
@JsonInclude(JsonInclude.Include.NON_NULL)
private Long boardCount;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Long storeCount;
public BoardCustomPage(
T content,
Long requestCursor,
Boolean hasNext,
Long boardCount,
Long storeCount
) {
super(content, requestCursor, hasNext);
this.boardCount = boardCount;
this.storeCount = storeCount;
}
}
현재 CustomPage가 모든 페이지에 들어가는 공통 요소이고 BoardCustomPage는 이를 상속 받아 Board를 내려줄 때 필요한 요소를 추가한 형태입니다.
@Getter
@RequiredArgsConstructor
@AllArgsConstructor
public class CustomPage<T> {
private T content;
private Long nextCursor;
private Boolean hasNext;
}
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BoardCustomPage<T> extends CustomPage<T> {
private CustomPage<T> customPage;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Long boardCount;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Long storeCount;
}
위와 같은 방식으로 상속으로 속성을 그대로 받아오는 것이 아닌 멤버 변수로서 넣는 조합의 방식으로 구현한 것입니다.
관련 링크 전달드립니다. https://tecoble.techcourse.co.kr/post/2020-05-18-inheritance-vs-composition/
중원님 의견 감사합니다~ 저렇게 사용해도 좋을 것 같네요
다만, 이후 요구 사항이 추가되거나 기능이 더 나왔을 때가 고민입니다
1. 확장성이 있는가? 요구 사항이 늘어났을 경우도 처리할 수 있나?
- count 말고 다른 필드값이 추가된다면?
- 클래스를 만들거나, 필드를 추가해야함
/**
* 커서 기반 페이지네이션 상태를 관리하는 클래스입니다.
*/
@Getter
@RequiredArgsConstructor
public class CursorPaginationResponse<T> {
private final Long nextCursor;
private final Boolean hasNext;
/**
* 페이지네이션이 없는 빈 응답을 생성합니다.
*
* @return 빈 {@link CursorPaginationResponse} 객체
*/
public static <T> CursorPaginationResponse<T> empty() {
return new CursorPaginationResponse<>(-1L, false);
}
/**
* 페이지네이션 상태를 생성합니다.
*
* @param data 데이터 리스트
* @param pageSize 페이지 크기
* @param idExtractor 항목에서 ID를 추출하는 함수
* @return {@link CursorPaginationResponse} 객체
*/
public static <T> CursorPaginationResponse<T> of(List<T> data, int pageSize, ToLongFunction<T> idExtractor) {
boolean hasNext = data.size() > pageSize;
Long nextCursor = -1L;
if (hasNext) {
T lastElement = data.get(pageSize - 1);
nextCursor = idExtractor.applyAsLong(lastElement);
}
return new CursorPaginationResponse<>(nextCursor, hasNext);
}
}
import java.util.List;
import java.util.function.Function;
/**
* 데이터 변환을 담당하는 클래스입니다.
*/
public class DataProcessor<T, U> {
private final Function<List<T>, U> transformFunction;
public DataProcessor(Function<List<T>, U> transformFunction) {
this.transformFunction = transformFunction;
}
/**
* 주어진 데이터를 변환 함수로 처리하여 반환합니다.
*
* @param data 변환될 데이터 리스트
* @return 변환된 데이터
*/
public U process(List<T> data) {
return transformFunction.apply(data);
}
}
/**
* 처리된 데이터와 페이지네이션 상태를 포함하는 응답 클래스입니다.
*
* @param <T> 페이지네이션 되는 개별 항목의 타입
* @param <U> 변환 함수 적용 후 반환되는 처리된 데이터의 타입
*/
@Getter
@RequiredArgsConstructor
public class ProcessedDataCursorResponse<U> {
private final U data;
private final Long nextCursor;
private final Boolean hasNext;
/**
* 페이지네이션과 데이터 처리를 수행하여 응답 객체를 생성합니다.
*
* @param data 변환될 데이터 리스트
* @param pageSize 페이지 크기
* @param idExtractor 항목에서 ID를 추출하는 함수
* @param transformFunction 데이터 리스트를 변환하는 함수
* @return 처리된 데이터를 포함하는 {@link ProcessedDataCursorResponse} 객체
*/
public static <T, U> ProcessedDataCursorResponse<U> of(
List<T> data,
int pageSize,
ToLongFunction<T> idExtractor,
Function<List<T>, U> transformFunction
) {
CursorPaginationResponse<T> pagination = CursorPaginationResponse.of(data, pageSize, idExtractor);
DataProcessor<T, U> processor = new DataProcessor<>(transformFunction);
if (pagination.getHasNext()) {
data = data.subList(0, pageSize);
}
U processedData = processor.process(data);
return new ProcessedDataCursorResponse<>(processedData, pagination.getNextCursor(), pagination.getHasNext());
}
/**
* 빈 응답을 반환합니다.
*
* @param processedData 처리된 빈 데이터
* @return 빈 {@link ProcessedDataCursorResponse} 객체
*/
public static <U> ProcessedDataCursorResponse<U> empty(U processedData) {
return new ProcessedDataCursorResponse<>(processedData, -1L, false);
}
}
정확한 예시가 하나 더 있으면 흐름을 보는데 도움이 될 것 같다는 생각이 들지만
위 방식의 확장성이 좋다는 부분과 유지보수에 좋은 측면에 대해 확 와닿지 않는 느낌이 있어 하나 예시를 들어 생성부터 반환까지의 로직을 플로우차트로 그려주시면 이해에 도움이 될 것 같습니다
중원님 감사합니다. 그리다 보니 제가 생각하는 방향이 조금 잡히네요 확장성은 둘 다 문제가 안되네요(제가 잘못 생각하고 있었어요)
다시 보니 제가 계속 걸리는 부분이 책임 분리라고 생각해요 DTO에서 Cursor 로직을 수행하는 것보다 따로 Resolver를 만들어서 동작시키는 게 책임 분리에 더 좋을 것 같습니다.
하지만 이 또한 개발자가 생각하는 DTO의 역할에 따라 달라질 거 같아. 취향차이로 생각이 들어요!
History
🚀 Major Changes & Explanations