-
Notifications
You must be signed in to change notification settings - Fork 1
가계부 내역의 상태관리에 대한 논의
대시보드 페이지에 대한 reducer를 구현하다가 가계부 내역 상태관리와 관련하여 문제점을 발견하여 논의해볼 사항으로 정리했습니다.
먼저, 진용님께서 가계부 등록을 구현하시면서 정의하신 가계부 State는 다음과 같습니다.
type TransactionState = {
transactions: {
loading: boolean;
error: Error | null;
data: TransactionModel[];
};
};
그리고 백엔드에서 월 가계부 내역을 받아왔을 때의 데이터 구조는 다음과 같습니다.
type MonthTransactionsResponse = {
totalIn: number;
totalOut: number;
mostOutDateDetail: {
amount: number;
date: number;
};
aggregationByDate: [number, { totalIn: number; totalOut: number }][];
transactionDetailsByDate: [number, TransactionModel[]][];
};
두 타입의 차이를 보면 가계부 State는 단순히 가계부 내역의 리스트만 상태 관리를 하고 있는데 실제로 관리가 필요한 State는 총 수입/지출, 최다 지출 등의 통계 데이터가 포함되고 리스트 또한 날짜별로 그룹화되어 있습니다.
논의할 점 1) 가계부 내역 상태에 대한 설계
- 상태관리를 전체적으로 어떤 데이터를, 어떤 구조로 관리할지 정하고 구현을 시작해야할 것 같습니다.
- 이번달 총 수입/지출, 최다 지출일 정보, 일자별 수입/지출 집계와 같은 통계 정보를 분리해서 관리할 것인지 하나의 상태로 관리할 것인지.
- 분리한다면 등록/수정/삭제후 각 통계 데이터를 업데이트 해주는 작업은 어디서? reducer?
논의할 점 2) 동일한 API의 응답데이터를 사용하는 페이지
동일한 API의 응답데이터를 사용하는 페이지가 있을 때 (대시보드 - 월 내역 리스트로 보기 - 월 내역 달력으로보기) 대시보드에서 fetch를 하고 다른 페이지에서 상태를 불러오는 구조가 되면 링크로 직접 접속했을 때 fetch된 데이터가 없는 상태가 됩니다.
이런 경우 fetch 전략을 어떻게 가져갈지 논의가 필요할 것 같습니다.
논의할 점 3) 가계부 등록/수정/삭제 요청 후 상태 관리
가계부 등록/수정/삭제 요청 후 통계 데이터를 변경하기 위해 다음과 같은 방법으로 업데이트를 해줘야 합니다.
- 월 가계부 내역 GET API를 새로 호출
- API 요청 결과에 따라 프론트에서 부분적으로 계산하고 업데이트
1번 방법의 경우 구현이 편리하다는 장점이 있지만 응답 데이터를 생성하는 절차가 복잡한 만큼 잦은 API 호출은 성능 저하로 이어질 수도 있을 것 같습니다.
2번 방법의 경우 날짜와 새로 업데이트 된 총합을 비교해가면서 가장 많이 소비한 날 등의 데이터를 부분적으로 업데이트를 할 수 있을 것 같은데 프론트에서 할일이 많아지겠지만 서버에서 응답 데이터를 생성하는 부담을 각 사용자의 디바이스 환경으로 분산시킬 수 있는 장점이 있습니다.
아니면 단순하게 요청 이후에 업데이트 된 전체 데이터에 대해 통계 데이터를 전체적으로 새로 계산하는 방법도 있을 것 같습니다.
만약 2번, 3번 방법을 쓸거라면 백엔드에서 받아오는 데이터도 단순히 월에 해당하는 가계부 내역 리스트만 응답하는 API로 변경을 해도 되지 않나 생각됩니다.
논의1) 가계부 내역 상태에 대한 설계
- 선택한 월에 따라서 내역을 관리해줘야 하니까 '년-월'을 키로하는 맵 구조가 되어야 할 것 같다.
- 리덕스 상태관리에서 맵으로 사용할 수 있는가?
- 선택한 월을 모두 상태로 등록하지 않고 하나의 상태를 변경해가면서 사용(아래 추가 논의사항 참고)
- 사용자가 조회한 내역을 모두 저장하고 있을 것인가? 사용자가 의미없이 많은 기간에 대해 조회를 하고 다시 사용하지 않는다면 메모리의 낭비가 될 것 같다.
-
가장 최근에 조회한 5개 내역정도만 관리해주는 방식은 어떤가요? (캐싱)
-
좋은것 같은데 구현 방식은 어떻게 될런지..?
-
가장 오래된 조회 항목을 밀어내는 식으로?
- (LFU vs LRU) => 공부해보겠습니다.
-
존재하는지는 어떻게? -> '년-월' 키만 따로 저장해서 최근 조회 항목을 관리하고 실제 데이터는 Map으로 저장해두면 될 것 같습니다.
-
리덕스에서 맵을 사용할 수 있는지?
- 캐시로 관리하려는 데이터가 5개 정도(소량)이니까 시간복잡도가 크게 중요하지 않을 것 같고 오히려 Map을 사용하는게 메모리 사용량이 더 많을 것 같다.
- 그냥 배열로 관리하기 -> '년-월' 키를 관리할 필요도 없을 것 같습니다.(? 구현하면서 확인해보기)
- 캐시 miss가 많이나면 키를 set 으로 관리하는게 좋고 miss가 많이 안날거라면 바로 배열을 순회하는게 좋을 것 같다.
- miss율은 측정해봐야 알 수 있을듯...
- 키를 관리하는 set은 reduce 모듈에서 전역변수로 생성해두고 참조용으로만 써도 괜찮을 것 같다.
//fetch를 하는 곳에서 (흐름만 참조..) import cache from 'cache'; if(cache) { dispatch(cache); } else { const data = await ...; dispatch(data); }
-
State 구조
const initialState: TransactionState = {
loading: false,
error: null,
totalIn: number,
totalOut: number,
mostOutDateInfo: { date:number, amount: number}, //새로 업데이트 된 aggregationByDate의 totalOut과 비교
aggregationByDate: [date, [{totalIn: number, totalOut: nubmer}]],
transactions: [date, TransactionModel[]], //date를 찾아서 push, 없으면 새로운 배열로 push
};
// push
const newTransactions = [...state.transactions];
newTransactions.push();
return {
...state,
transactions: [...newTransactions];
}
//action 생성함수
const getTransactions = (date) => {
dispatch({ type : GET_TRANSACTION }
try {
if(cache[date]) dispatch({type : GET_TRANSACTION_SUCCESS, payload : cache[data]});
const data = await transactionAPI.getTransaction(date);
dispatch(addTransaction(data)); // 해당달이 아니면?
} catch(e) {
}
}
논의2) 동일한 API의 응답데이터를 사용하는 페이지
- 페이지에 접속했을 때 상태가 없으면 대시보드로 이동시킨다. (❌)
- 각 페이지마다 페이지에 접속했을 때 상태가 저장된게 있는지 체크를 하고 없으면 fetch 요청, 있으면 상태를 가져온다.
- 같은 API를 사용하는 페이지의 라우터에 미들웨어 처럼 fetch 요청하는 컴포넌트를 구현해서 등록해둔다.
- 오 좋은 방법인 것 같습니다.
- 테스트 해보기
논의할 점 3) 가계부 등록/수정/삭제 요청 후 상태 관리
- 업데이트가 되면 프론트에서 부분적으로 업데이트를 진행
월별 모두보기 페이지의 역할이 분명한가?
- 단순히 내역을 보여주기만 하는 역할을 하는 페이지
- 달력 보기에서도 리스트를 보여주고 시각적으로도 한눈에 파악하기가 좋음
- 없어도 될 것 같은 페이지 → 구현에서 제외
대시보드를 현재 월로 고정했을 때 상태관리가 복잡함
- 벤치마킹 서비스인 토스에서 대시보드의 월 변경이 가능함.
- 대시보드에서의 월 변경과 달력에서의 월 변경이 서로 공유됨.
- 선택된 월을 공유할 경우 상태관리를 이번달/조회용으로 구분하지 않아도 되는 이점이 있다.
- 캐싱하기도 용이함.
선택된 월 상태를 공유했을 때 통계 페이지의 처리는?
- 통계 페이지로 넘어갈 때 현재 달을 기준 통계를 보여준다 .
- 주간별/월간별 데이터는 추후에 확장을 고려하자
- 확장을 고려해서 현재 api는 그대로 두고 front에서 start, end date 계산해서 보내기!
대시보드를 월 변경으로 하였을 때 고정지출에 대한 변경사항
- 고정지출에 대한 변경
- entity 자체에 월에 대한 column이 추가되어야함
- 완료/예정 기능을 배제