iamkanguk.dev

[맵필로그] 캘린더 화면 로직 좌충우돌 기록! 본문

사이드 프로젝트/맵필로그

[맵필로그] 캘린더 화면 로직 좌충우돌 기록!

iamkanguk 2023. 11. 9. 20:00

최근에 express를 가지고 프로젝트를 진행해보고 요즘에는 Node.js 기반 백엔드 개발하시는 분이시라면 알고 계시는 NestJS를 가지고 프로젝트를 시작해 봤다. 프로젝트의 주제는 캘린더 기능 및 일정을 기록할 수 있는 애플리케이션이다. 모임 기능도 있지만 모임 기능은 추후 2차 개발에서 진행될 계획이다.

 

서론은 이정도로 마치고 캘린더 화면을 어떻게 구현을 했는지, 어떻게 마무리를 했는지에 대해서 쭉 정리를 해보려고 한다.

 

참고로 길이 아주 길 수 있으니,,, 참고!

 

캘린더 화면

기획하고 있는 애플리케이션 캘린더 화면

 

사진을 보시면 달력에 일정블록들을 배치할 수 있는 그런 화면이다. 일정이 추가될 때마다 유동적으로 블록을 잘 배치해줘야 하는 부분이 제일 골치 아픈 부분이다.

구현 스토리

1. 처음 필자가 생각한 로직

일단 설명하기 전에 일정 Table을 공유를 해야 할 것 같다.

 

일정 테이블에는 색깔을 정해줄 수 있기 때문에 색깔 아이디, 그리고 일정을 작성한 사용자의 아이디가 기본적으로 있으며 일정 제목과 시작날짜와 종료날짜, 그다음에 isAlarm은 일정에 알림 기능을 적용할 수 있는데 알림을 적용했는지 안 했는지에 대한 여부이다.

 

원래 맨 처음에 API를 설계할 때는 저 화면에 있는 데이터들을 각 날짜마다 어떤 일정들이 있는지를 보여주려고 했다. 다시 말해서 5월 4일에는 일정 몇개가 있고, 5월 6일에는 또 몇 개가 있고.. 이런 식으로 보여주려고 했었다.

 

하지만 굳이 그렇게 해야하나? 싶기도 했었고, 네이버와 구글 캘린더 API 문서를 한번 봤었는데 더욱 간단하게 Response를 줘도 괜찮겠다 싶었다. 위의 사진을 보면 일정이 총 9개인데 이제 일정의 startDate와 endDate만 제공해도 클라이언트에서 알아서 블록 배치를 할 수 있겠다고 생각했다.

 

그래서 Response를 다음과 같이 제공했었다. 예시를 드려보자면!

result: [
   {
       "scheduleId": 일정 아이디,
       "title": "일정 제목",
       "startDate": "YYYY-MM-DD",
       "endDate": "YYYY-MM-DD",
       "colorId": 색깔 고유값,
       "colorCode": "색상 코드값"
   },
   { ... },
]

 

2. 이렇게 Response를 주시면 안될 것 같다.

현재 맵필로그 프로젝트에서는 ios와 Android 모두 출시를 준비하고 있다. 확인을 해보니 ios 개발자님은 완전 커스텀으로 캘린더를 구현하셨고, Android 개발자님은 특정 캘린더 라이브러리에 일부분 기능을 위해 커스텀으로 구현을 하셨다고 들었다.

 

ios 개발자님은 위의 Response를 가지고 캘린더 블록 배치를 구현하셨다고 들었다. 물론 여러 테스트 케이스를 더욱 테스트해봐야겠지만 일단은 그렇게 답변을 받았었다. 하지만 Android 개발자님은 Response 형태를 아예 바꿔야 할 것 같다고 연락을 받았다.

 

Android 개발자님은 Response 형태를 어떻게 요청하셨냐면..!

 

- 각 날짜별로 어떤 일정이 보여줘야 한다.

- 블록들의 순서도 알맞게 전달을 해줘야 그대로 캘린더에 블록을 배치시켜서 랜더링을 할 수 있다.

- 또한, 각 일정에 대해 해당 날짜가 일정의 시작날짜인지, 종료날짜인지 그리고 중간 날짜인지를 알려줘야 한다. 단, 일요일 날짜는 무조건 START로, 토요일 날짜들은 END로 보내주시면 될 것 같다.

 

참고로 START와 END는 블록들을 연결하는 것 때문에도 그렇고 예를 들어 일정이 2주차에서 3주 차까지 연결될 때 3주 차의 일정 이름이 무조건 일요일에 배치가 되기 때문에 그런 것도 있다는 것 같다.

 

3. 개발 시작 및 문제점

< 각 주의 주말을 START와 END로 처리 >

 

일단 해당 년도와 월에 일요일은 며칠이 있고, 토요일은 며칠이 있는지 알아야 한다. 여기서 중요한 점은 이전 달과 이후 달의 날짜도 포함이 되어야 한다는 것이다. 예를 들어보면 위의 사진에서 5월 달력이지만 첫 주에는 4월 30일과, 막주에는 6월 3일 토요일까지 포함이 되어야 한다.

 

import * as moment from 'moment';

// 이번달의 첫째 날 요일을 구하는 함수
export const getFirstDay = (year: number, month: number): number => {
  const currentDate = moment({ year, month: month - 1 });
  const firstDayOfMonth = moment(currentDate).startOf('month');
  return firstDayOfMonth.day();
};

// 이번 달의 일요일, 토요일 리스트를 가져오는 함수 (달력 기준)
export const getWeekends = (year: number, month: number): string[][] => {
  const firstDay = getFirstDay(year, month);
  const firstSunday = moment({ year, month: month - 1 }).subtract(
    firstDay,
    'day',
  );
  const firstSaturday = moment({ year, month: month - 1 }).add(
    6 - firstDay,
    'day',
  );

  const result = [
    [firstSunday.format('YYYY-MM-DD'), firstSaturday.format('YYYY-MM-DD')],
  ];

  for (let i = 0; i < 4; i++) {
    result.push([
      firstSunday.add(7, 'day').format('YYYY-MM-DD'),
      firstSaturday.add(7, 'day').format('YYYY-MM-DD'),
    ]);
  }

  if (
    firstSunday.month() + 1 === month &&
    firstSaturday.month() + 1 === month
  ) {
    result.push([
      firstSunday.add(7, 'day').format('YYYY-MM-DD'),
      firstSaturday.add(7, 'day').format('YYYY-MM-DD'),
    ]);
  }

  return result;
};

 

위의 코드는 Javascript의 moment.js 라이브러리를 통해 구현했다. 코드가 조금 더러울 수도 있는 점 양해부탁드립니다 :)

 

시나리오를 하나 제공을 해보도록 하겠다.

- 2023년도 5월의 첫날은 5월 1일 월요일이다. 참고로 월요일은 숫자 1이다. 달력을 보면 첫날이 월요일이면 그 앞에 전달의 일요일 날짜가 있다.

- 그렇기 때문에 5월 1일에서 요일 코드만큼 빼면 된다. 그러면 달력상의 첫 일요일 날짜를 구할 수 있다. 토요일도 마찬가지이지만 다른 점은 빼는 게 아니라 더한다는 것이다.

- 첫 일요일 날짜와 토요일 날짜를 구했으면, 그 다음주들의 첫 주를 제외하고 넷째 주까지 있을 수 있고, 다섯째 주까지 있을 수 있다. 그렇기 때문에 일단 4번은 무조건 반복문을 돌아야 한다. 이때 위에서 구한 첫째 주의 일요일과 토요일 날짜에서 7일을 더해주면 된다.

- 여기서 고려해야 할 점은 5째주까지 있는 경우이다. 대표적으로 2023년도 12월 달력이다. 예를 들어 마지막주에 31일 일요일과 그다음 달의 토요일이 있다고 가정해 볼 때 7일을 더했을 때 일요일은 우리가 조회하려는 달이고, 토요일은 그다음 달이 나오기 때문에 한번 더 반복문을 돌아줘야 하는 것이다. 이유는 어떤 달력에서도 토요일 기준으로 다음 달 날짜가 나오지 일요일 기준으로 다음 달이 나오는 달력은 없기 때문이다.

 

< 지금까지의 상황 총정리 >

 

그래서 위의 코드와 Query문을 잘 작성해서 START와 END처리를 할 수 있었고 기본적으로 일정들을 조회할 수는 있었다. 여기서 문제점은 위의 사진에서 일정 사이의 공백과, N번째 주에서 N+1번째 주로 넘어갈 때 블록 순서를 그대로 가져가는 것이다.

 

백엔드 쪽에서 어떻게 저 일정 사이의 공백을 구현해야 하는지 많은 고민을 하게 되었다. 그리고 블록 순서에 대해 이야기 하자면 일정은 startDate을 가지고 orderby를 하고 있어서 블록이 위에서 아래로 내려간다.

 

예를 들어, 위의 사진에서 주황색 일정이 부산여행 일정보다 startDate가 빠르다. 그러면 첫 번째 주차에서는 잘 정렬이 되어있는데 2번째 주를 보면 주황색 블록이 위에 있어야 한다. 왜냐면 startDate가 빠르기 때문이다. 하지만 지금 부산여행 일정이 위로 올라와있다. 이유는 1주 차에서 부산여행 일정이 위에 배치되어 있기 때문이다.

 

그래서 다시 말하면 지금은 각 날짜의 일정은 잘 나오지만 일정의 블록 순서가 구현이 안된다는 점이다.

 

4. 너무 많은 시간소요와 그에 따른 스트레스 ㅠ

2주가 넘게 소요되고 약 1달이라는 시간이 소요되었다. 공백과 블록 순서를 어떻게 구현을 해야 하는지 너무 감이 안 잡혀서 많은 개발 커뮤니티에 들어가서 여쭤보았다. 하지만 대부분의 개발자님들에게 처음에 설계한 Response 대로 클라이언트에게 전달을 하고 클라이언트에서 직접 블록을 배치하는 것이 맞다는 답변을 받았고 실제로 그렇게 구현을 하신 Android 개발자님도 계셨다.

 

그래서 정중하게 우리팀 Android 개발자분에게 공유를 드리고 직접 블록을 배치하는 로직도 한번 고민해 달라고 요청을 드렸다.

이후에 잠깐 머리도 식힐겸 다른 도메인을 개발하고 있었는데, 결국 구현하기 조금 어려울 것 같다고 답변을 받았다.

 

5. 그래서 이 문제점은 어떻게 구현을 했는지?

팀원들과 매주 화요일마다 회의를 하는데, 오랫동안 이 캘린더에 대해서 회의를 진행했다. 결국 일단은 배포를 지금 생각하고 있기 때문에 안전책으로 다음과 같이 화면구성을 변경했다. 위의 화면은 블록이 아닌 점으로 구현을 했다. 하지만 이 화면은 Android 한정이다. 다시 말해서 iOS에서는 블록 버전의 화면으로 배포가 되고, Android에서는 위와 같이 점 버전의 화면으로 배포가 된다는 것이다. 위와 같이 점으로 된다면 필자가 생각한 Response로 구현이 되기는 한다고 답변을 받았다. 

 

6. 느낀점

일단 아직 배포는 안 했지만 너무너무 아쉽다.  



뭔가 구현을 할 수 있을 것 같지만 안되고,, 시간이 너무 오래 걸리다 보니 일단 안전책으로 이렇게 하긴 했지만 2차 배포에서는 꼭 블록 배치가 될 수 있도록 기능을 구현해보고 싶다.

 

그리고 생각의 전환을 하게 된 계기가 있는데 캘린더 조회 로직을 고민해 보면서 어쩌다 보니 클라이언트에서 구현을 해야 하는 거 아닌가?라는 생각이 계속계속 들었었는데 사실 이거는 어느 파트에서 해야 한다. 어느 파트에서 해야 한다는 절대절대 없는 것 같다. 그냥 갈라 치기밖에 되지 않는 것 같다.

 

원래 두 파트를 나누지는 않았는데 어쩌다 보니 그렇게 생각하게 된 것 같다. 이 부분에 대해서 반성을 하게 되었다.

 

<2023.11.09 추가>

 

Android 개발자님과 로직을 또 얘기를 해봤었는데.. 이런 방법은 어떤가 싶다.

남은 기능들을 전부 구현해 보고 적용을 해볼까 한다.

 

(1) 주 단위로 일정들을 가져온다. 이때 일정은 시작 날짜를 기준으로 정렬한다.

(2) 가져온 일정들을 일자별로 계산 배열에 추가한다. 예를 들어서, 일요일에는 일요일에 시작되는 일정 하나가 계산용 배열에 들어가고, 월요일에는 월요일에 시작되는 아이템 하나가 추가되는 식인데 아이템은 일정이 종료되기 전까지 스택 자료구조를 사용한다.

(3) 종료된 일정이 있는 경우에는 해당 자리를 지우는 것이 아닌 공백으로 둔다.

(4) 이후 종료된 일정이 있다면 그다음 일정 (계산용 배열에 2번 일정까지 있다면 3번 일정)을 계산용 배열의 공백자리에 추가해 준다.

 

위 방법을 사용하면 뭔가 공백자리도 해결할 수 있을 것 같고  빈 공간에 일정까지 추가할 수 있을 것 같다..?

물론 조회할 때 로직이 빡빡하기 때문에 캐싱 등 최적화가 필요할 것 같기는 하다.

 

되게 많은 걸 느낄 수 있는 기능이었고 많이 아쉽긴 하지만 그래도 포기하지 않고 끝까지 내가 맡아서 해결해보고 싶다.

 

긴 글 읽어주셔서 감사합니다~!