-
Github 리드미에 달력 표시하기 - 1탄Back-End/Python 2023. 5. 21. 19:24
Github README.md에 달력이 표시되면 어떨까 하는 생각에 혼자서 사이드 프로젝트를 진행해 보았습니다. 마침 파이썬을 공부해보고 싶었고 fast api도 써보고 싶었기 때문에 간단하게 만들어 보려고 했지만 전혀 간단하지 않았던😅 저의 우당탕탕 개발스토리 들려드리도록 하겠습니다.
🐣 프로젝트 소개
이름 : notion2svg
설명 : 노션에서 기록한 달력을 Github README.md에서 볼 수 있도록 개발
Skills : python v3, fast api, notion api
🤔 왜 프로젝트 제목이 notion2svg인가요?
Github 리드미에 달력을 볼 수 있도록 하는 프로그램인데 왜 이름이 notion2svg인지 궁금하실 것 같습니다. 결론부터 말씀드리자면 MVP(Minimum Viable Product)를 최우선적으로 달성시키기 위해 notion2svg라는 이름을 지었습니다. (
쉽게 말해서 다른 기능들 넣기 귀찮았다)만약에 추후에 구글 캘린더나 다른 캘린더 플랫폼들도 고려를 한다면 프로젝트 이름을 calendar2svg 로 수정할 수도 있고 notion의 추가적인 기능들을 리드미에 넣을 수 있도록 고려한다면 notion2gihub 이런 식으로 네이밍을 변경할 수 있을 것 같습니다. 일단 현재는 notion2svg로 가려고 합니다.
그리고 svg로 만드는 이유는 디자인이 깨지지 않는 달력을 만들고 싶었기 때문입니다. 처음에는 마크다운 테이블을 이용하려고 했지만 글자 길이에 따라 테이블 컬럼이 일정하지 않게 되어 달력 디자인이 엉망으로 나왔습니다. 그래서 처음부터 달력 사이즈와 표를 일정 크기에 맞춰 놓고 구현하는 것이 좋겠다 싶어서 반응형에 유연하게 대처할 수 있는 vector기반의 미디어 타입인 svg를 선택하게 되었습니다.
🛠️ 개발 도구 선정
가장 먼저 언어는 python을 선택하였습니다. 사실 가장 자신 있는 백엔드 기술은 java 지만 AI 필드에서 일하고 있기 때문에 python을 배워놓으면 추후에 도움이 될 것 같아서 python으로 선택하였습니다.
(java로 개발할걸 그랬습니다)그리고 실제 DBMS로 쓰고 있는 notion과 연결할 수 있는 중요한 데이터 파이프라인으로는 당연히 notion api를 사용했고 svg를 response로 내보내줄 컨트롤러가 하나 필요 해서 서버용 프레임워크는 Django, Flask 등등 고민이 많았지만 저는 비교적 빠르다고 소문난 fast api를 채택하였습니다.
🌐 API 서버 설계
간단한 API 서버를 구축하는 것이기 때문에 단순한 MVC 디자인 패턴을 가지도록 설계하였습니다. 사실 API호출하고 서비스단에서 데이터를 가공하는 게 전부라 거창하게 무언가를 설계했다고 말하기 민망하긴 합니다.
지금 단계는 아니지만 추가적으로 보완할 게 있다면 달력 model을 만들고 추상화까지 해보면서 천천히 달력 템플릿을 스케일 아웃해 볼 예정입니다.
🪐 렌더링 알고리즘 구현
이 프로젝트의 가장 핵심이라고 볼 수 있는 부분은 notion_api의 데이터를 가져와서 notion calendar와 어느 정도 유사하게 만들 수 있는가입니다. notion과 완전히 유사하게 만들 수는 없었겠지만 나름 상상력을 더해 notion을 클론 코딩해 보았습니다.
캘린더 Wrapper 로직
거창하게 알고리즘 로직이라고 내세웠지만 사실 어려운 부분은 크게 없습니다. 학교나 학원 등에서 아마 누구나 달력 프로그램 하나쯤은 만들어보셨을 겁니다.
현재 년도와 월을 확인하고 현재 월에 해당하는 일 수만큼 loop를 돌면서 오늘 날짜에 해당하는 x축, y축 좌표를 찾아서 디자인하면 됩니다.
현재 년도와 월은 라이브러리를 쓰면 되고 x축, y축은 각각 일별 loop, 주별 loop의 인덱스를 활용하면 정말 간단하죠?
캘린더 Inner 로직
오른쪽 순서도는 위의 캘린더 Wrapper 로직에서 'notion page 개수만큼 svg 디자인' 부분에 해당하는 상세 알고리즘입니다.
Calendar의 일별로 수행하는 로직은 조금 더 복잡해집니다. 달력에 페이지를 하나씩 꽂아 넣어야 되기 때문이죠.🥹
그런데 문제는 그냥 꽂아 넣기만 하면 되는 것이 아닙니다. notion 캘린더를 보면 다음과 같이 notion 페이지의 시작 날짜와 끝 날짜에 따라 가로로 merge 된 디자인을 볼 수 있습니다. 또는 한 날짜에 두 개의 페이지가 공존하는 경우도 있겠죠. 따라서 각 경우에 따라 페이지 블럭이 가지는 x축, y축, width, height값을 각각 계산해야 합니다.
저도 똑같이 달력에 나타내고 싶어서 어떻게 표현할까 고민을 참 많이 했습니다. 그래서 처음에는 간단하게 가자는 생각으로 dictionary(자바에서는 map)를 사용해서 일별로 페이지를 보관했습니다.
[ { "2023-05-01" : ... } ... { "2023-05-06" : [notion2svg project] }, { "2023-05-07" : [notion2svg project] }, ... { "2023-05-13" : [독서, 축구] }, { "2023-05-14" : [블로그] }, ... { "2023-05-31" : ... } ]
그러나 위와 같이 짰을 경우 svg 디자인을 하기 까다롭다는 문제점이 있었습니다. svg의 특징 중 하나가 그린 순서대로 렌더링 됩니다.(즉 스크립트의 top down) 예를 들어 아래와 같이 달력 템플릿을 먼저 생성한 후 그 위에 페이지들을 드로잉 해야 합니다.
만약 순서를 바꾸면 달력의 구분선들이 페이지 디자인 영역을 침범해서 보기 좋지 않게 됩니다. 쉽게 생각해서 스케치북에 그림을 그리는 순서와 동일하다고 생각하시면 쉽습니다.
그런데 이 렌더링 방식이 왜 문제가 될까요? 아래와 같은 디자인을 구현한다고 상상해 봅시다.
루프를 돌면서 19일에 '블로그'라는 페이지를 발견하면 먼저 디자인을 채워 넣고 20일이 되면 또다시 디자인을 수정해야 합니다. 문제는 이전 날짜부터 이어져온 디자인이라는 것을 알기 위해 이전 루프의 데이터를 기억해야 된다는 것입니다. 만약 이전 루프의 데이터를 기억하지 못한다면 다음과 같이 분리된 디자인이 나타날 것입니다.
그래서 저는 이전 날짜에서부터 이어진 페이지라는 것을 알 수 있도록 페이지의 시작날짜에 맞춰 미리 디자인을 만들어야겠다는 계획으로 변경하였습니다. 그래서 다시 리스트 매핑 방식으로 돌아가서 앞서 소개한 리스트의 구조를 다음과 같이 바꿔보았습니다.
[ { "2023-05-06" : [notion2svg project] }, { "2023-05-13" : [독서, 축구] }, { "2023-05-14" : [블로그] } ]
확실히 앞선 방법보다 리스트가 간소화되고 직관적으로 변했습니다. 이제 리스트는 notion api를 호출해서 불러온 page 개수만큼만 가지게 되므로 불필요하게 날짜별로 페이지를 쪼개지 않아도 되었습니다. 키 값의 날짜는 그대로인데 무엇이 달라졌냐고 여쭤볼 수 있을 것 같은데 자세히 보면 월에 해당하는 일별 데이터가 아니라 notion api에서 전달해 준 페이지의 시작 날짜(start_date)를 key 값으로 사용하였습니다. 그리고 value 값으로는 페이지에 해당하는 이름, 아이콘 등의 데이터들을 저장했습니다.
{ start_date: "2023-05-06" end_date: "2023-05-07" name: "notion2svg project" icon: icon }
이렇게 데이터 모델링을 하고 나니 확실히 좀 더 메모리 절약적이고 유지보수할 때도 훨씬 효율성이 좋았습니다. 그리고 end_date에서 start_date를 뺀 값으로 가로길이를 알 수 있게 되므로 가로로 merge 된 디자인 역시 구현할 수 있게 되었습니다. 또한 페이지의 시작날짜에 디자인을 딱 한 번만 해주면 되는 훨씬 가벼운 로직으로 탄생하게 되었습니다.
그러나 문제는 아직 남아있었습니다. 시작날짜에 미리 디자인을 해놓으니까 앞서 얘기한 순서대로 렌더링 되는 이슈 때문에 한 날짜에 여러 페이지가 존재하는 경우 다음과 같이 페이지가 겹치는 이슈가 발생했습니다.
지금 이대로라면 x축, width, height값은 알 수 있지만 각 페이지별 y축은 알 수 없다는 문제점이 있었습니다. 위의 이미지를 토대로 설명드리자면 20일 날 '블로그'라는 페이지가 해당영역을 사용하고 있음을 '자바 18 공부' 페이지가 알고 있어야 하는데 현 상태로는 사용 중인 공간이다라는 것을 알 방법이 없어서 결국 이전 루프의 데이터를 기억해야 된다는 점은 매한가지였습니다. 그래서 다시 돌아가야 하나 생각이 많아졌습니다.. 그러나 불현듯 하나의 자료구조가 머릿속을 스치고 지나갔습니다.
Stack을 사용하면 되지 않을까?
y축 문제를 해결하기 위해 Stack의 길이를 사용하면 문제를 해결할 수 있지 않을까 해서 실행에 옮겨봤습니다.
만약 end_date가 start_date보다 나중 날짜면 Stack에 쌓고 루프를 돌다가 end_date가 현재 날짜에 도달하면 Stack에서 pop을 하는 방식으로 구현을 하니 드디어 페이지의 y축을 구할 수 있게 되었습니다. 즉 한 날짜에 여러 페이지들이 존재하더라도 페이지들 각각의 y축을 계산할 수 있게 된 것입니다.
그래서 정리하자면 페이지를 디자인하기 위한 데이터 값은 다음과 같이 구할 수 있었습니다.
x축 : 일별 loop 인덱스 * 페이지 길이
y축 : ( 주별 loop 인덱스 * 날짜 블럭의 높이 ) + ( stack 사이즈(length) * 페이지 높이 )
width : ( end_date - start_date + 1 ) * 페이지 길이
height : 페이지 높이
🌠 회고하기
사실 만들기 시작한 것은 작년이지만 항상 끝내야지 생각만 하다가 이번달에 시간을 근근이 투자해서 1차 배포를 할 수 있었습니다. 생각보다 버그랑 이슈가 많아서 애먹었지만 그만큼 애정도 많이 가고 여러 자료구조와 알고리즘에 대해 생각해 볼 수 있었던 재밌었던 개인 프로젝트였습니다.
사실 위에서 정리한 것 외에도 좀 더 디테일하게 들어가면 notion page에서 페이지 조회할 때 불러오는 쿼리는 어떤 식으로 작성해야 될까? 일주일이 끝나는 시점(일요일) 이후에는 stack와 width를 어떻게 관리할 것인가? 텍스트의 길이가 페이지의 길이를 넘어간다면 어떻게 처리할 것인가? 등등 생각해 봐야 될게 좀 있는 문제들이 있지만 블로그 내용에 다 담을 수가 없어서 제가 생각한 핵심 내용들만 간략하게 정리해 보았습니다.
혹시 위의 내용들이 궁금하다면 제 프로젝트 커밋로그나 코드를 참고해 주시고 피드백이 있으시다면 PR 또는 이슈, 메일, 댓글 등 무엇이든지 환영합니다 🤗
https://github.com/chucoding/notion2svg
다음 글에서는 Github 리드미에 달력 표시하기 2탄으로 넘어가서 서버 응답 시간 이슈, Github 새로고침 이슈, Github에서 이미지 사용 이슈 등 운영 측면에서 있었던 이슈들에 대해서 설명드리도록 할 테니 2탄도 기대 많이 해주시기 바랍니다. 😊
'Back-End > Python' 카테고리의 다른 글
Github 리드미에 달력 표시하기 - 2탄 (0) 2023.05.30 Python 프로젝트에 Prettier 적용하기 (0) 2023.05.05