들어가기 전에
아래는 Spring Boot + Vue.js 환경에서 이루어진 내용입니다.
개요
필자는 2021년부터 덴티아이라는 서비스를 3년간 운영해오고 있다.
덴티아이는 나라에서 지원하는 학생들의 치과주치의 사업을 전산화하는 시스템이다.
사업은 연 단위로 진행되고, 각 기관에서는 작성된 서식들을 특정한 기간(?) 동안 보관해야 한다고 한다.
따라서 덴티아이에는 작성된 서식들을 PDF로 변환한 후 기관별로 연말에 일괄적으로 다운로드할 수 있는 기능이 있다.
이러한 일괄 다운로드 기능의 구현 방식에 대해 변화한 과정을 소개한다.
서식 다운로드 및 인쇄
덴티아이에는 학생의 서식을 실시간으로 다운로드하거나 인쇄할 수 있는 기능이 있다.
이 기능은 작성된 서식을 보는 페이지에서 해당 화면 자체를 프론트에서 직접 보이는 html을 다운로드하도록 구현하였다.
이때, 요구사항은 아래와 같다.
- 다운로드 기능 : PDF 파일로 다운로드되어야 함
- 인쇄 기능 : 바로 프린터를 선택하고 인쇄할 수 있는 팝업이 노출되어야 함
작업하다가 알게 된 사실인데, javascript의 window.print()
를 이용한 브라우저에서 제공하는 기본 인쇄 기능을 사용하게 되면 최초에는 그래픽이 다 깨져 보인다.
이때에는 사용자가 직접 해당 인쇄 팝업에서 설정 더보기 > 배경 그래픽을 체크하도록 안내해야 한다.
기관 사용자의 경우 주로 연령대가 높은 분들이 사용하기 때문에 이러한 방법을 안내하기에는 무리가 있었다.
따라서 html 영역을 그대로 잡아서 바로 인쇄하도록 제공하는 건 어려워졌다.
배경 그래픽 문제를 해결하는 방법으로 서식을 직접 이미지로 변환한 후 해당 이미지로 인쇄하도록 하는 방법을 떠올렸다.
하지만 서식을 통째로 이미지로 변환 후 프린트하게 되면 다음 페이지로 넘어가는 부분을 직접 설정할 수 없어 중간에 글씨가 잘리는 현상을 피할 수 없다.
이러한 문제로 고민하던 중 회사 동료가 비슷한 경험을 통해 해결한 과정이 있다고 하여 관련 포스팅을 전달받았다.
해당 방법은 html을 블록 단위로 나누어 html2canvas
를 통해 각 블록의 이미지를 만들고 PDF에 하나씩 추가하면서 페이지 높이를 초과하는 경우 다음 페이지에 추가하는 방식이다.
이 방식을 활용하여 다운로드는 PDF를 동일한 방법으로 만들어 다운로드하도록 구현했고,
인쇄는 만들어진 블록 단위 이미지들을 가지고 인쇄용 html 바탕에 추가 후 새 창에서 window.print()
를 호출하여 인쇄하도록 구현했다.
PDF 저장
덴티아이에는 웹을 사용하는 기관의 서식 다운로드뿐만 아니라 앱을 사용하는 학생/학부모의 서식 다운로드 기능도 존재한다.
해당 기능의 요구사항은 아래와 같다.
- 기관이 웹에서 서식을 작성하면, 사용자가 앱에서 작성한 서식의 PDF 파일을 다운로드할 수 있다.
- 일반적인 문서 형식의 PDF여야 한다. (서식 특성상 앱 UI를 그대로 PDF로 만들기에는 무리가 있다.)
문서 형식의 PDF여야 하는 만큼 웹에서 PDF를 만드는 로직을 차용할 수 있을 것이라고 판단했다.
또한 웹에서 서식이 작성되면 실시간으로 사용자가 확인할 수 있어야 하는데,
이 부분은 기관에서 서식을 작성하고 제출할 때 PDF를 바로 생성하여 스토리지에 저장하는 방식으로 해결하기로 했다.
기존 서식 제출 API에 PDF 파일까지 추가하기에는 무리가 있어 PDF를 스토리지에 저장하는 API는 따로 구성했다.
또한 PDF 다운로드 기능은 위에서 구현한 대로 기관이 서식을 확인하는 웹 내 팝업에 존재했는데,
그 부분을 최대한 살려 아래와 같은 방식으로 구성했다. (구현하는데 애를 좀 먹었다..)
- 기관이 서식 제출 버튼을 클릭하면 로딩 화면을 노출하고, 보이지 않게 서식 확인 팝업을 띄운다.
- 서식 확인 팝업이 서식 제출을 통해 띄워졌다면 바로 PDF를 생성하고 스토리지에 저장한다.
- 스토리지 저장이 성공했다면 기존 서식 제출 폼에 알리고, 해당 폼에서 기존 로직 그대로 서식을 제출한다.
- 사용자가 앱에서 스토리지에 저장된 PDF를 다운로드한다.
서식이 먼저 제출되고 이후 PDF가 생성되는 순서라면 PDF 생성과정에서 오류가 발생한 경우 사용자가 PDF를 다운로드하지 못할 수 있기 때문에 PDF 생성 로직이 우선한다.
팝업과 기존 폼의 상태 확인은 vuex
상태값을 변경하는 식으로 알리고,
watch
를 통해 확인하여 이후 로직을 수행하는 방식으로 구성했다.
2021년 - 일괄 다운로드 첫 도입
2021년은 덴티아이가 php 환경에서 Spring Boot + Vue.js로 새로 개발되고,
또한 일괄 다운로드 기능이 처음으로 도입된 해이다.
따라서 비슷한 기능을 개발해 본 적이 없다 보니 시행착오를 많이 겪었다.
일괄 다운로드는 연말에 해당 기관에 소속된 학생의 서식을 종류별로 zip 파일을 통해 다운로드하는 기능이다.
다행히 사업 일정에 의해 일괄 다운로드 전에 서식 제출 기능은 막히기 때문에 실시간으로 작업할 필요는 없다.
따라서 스토리지에 저장된 PDF를 zip으로 묶어 다운로드하는 것으로 결정했다.
먼저 위에서 작업한 내용처럼 위에서 언급한 서식은 제출 시 PDF가 스토리지에 저장되고 있었지만,
사용자가 앱에서 직접 등록하는 서식 및 앱에서 PDF로 다운로드하지 않는 서식도 존재한다.
따라서 일괄 다운로드를 진행하려면 그러한 서식들도 모두 PDF로 스토리지에 저장되어있어야 하기 때문에 아래와 같은 방식으로 대응했다.
- 각 서식별로 PDF가 없거나 PDF 생성 이후 서식이 수정된 학생 목록을 가져오는 API 추가
- 해당 목록에서 반복을 돌려 프론트에서 서식을 생성하고 스토리지에 저장
위와 같은 방식으로 사내 안 쓰는 컴퓨터를 여러 개 세팅하여 몇 달 동안 밤새 열심히 돌렸다. (아쉽게도 작업 화면을 찍어둔 사진이 없다..)
PDF 저장이 완료되면 zip 파일만 정상적으로 생성하고 다운로드하면 끝이다.
하지만 당연하게도 PDF의 개수가 많아 zip 파일도 실시간으로 생성할 수는 없다.
따라서 백엔드에서 zip 파일을 생성하는 로직을 만들어 서식 제출 기능이 막힌 뒤에(PDF 변경사항이 없을 때) 해당 로직을 수행하여 스토리지에 zip 파일을 업로드한 후 일괄 다운로드 기간에 다운로드할 수 있도록 구성했다.
(zip 파일 업로드도 2주는 걸린 것 같다.)
2022년 - 프론트 PDF 생성 방식 변경, 백엔드 직접 PDF 생성 추가 등
2021년을 어찌어찌 잘 마무리하고 2022년 일괄 다운로드를 위해 개선사항을 도출했다.
2021년 구성의 문제점은 아래와 같다.
1. 이미지를 PDF에 삽입하다 보니 PDF의 용량이 크다.
위에서 생성된 PDF의 용량은 평균 400KB 정도였다.
단일 파일로는 크게 문제가 되지 않지만 일괄 다운로드를 위한 zip 파일로 구성했을 때 크기가 상당히 부담이었다.
하나의 zip 파일에 10,000개 정도의 PDF가 들어가는 경우도 존재했는데 이 경우 간단히 계산해도 4GB의 zip 파일이 구성된다.
첫 해다 보니 검수를 위해 zip 파일이 생성되면 확인 후 스토리지 콘솔에서 직접 파일을 올리곤 했는데,
NCP의 경우 스토리지 콘솔에서 직접 올릴 수 있는 파일의 용량은 최대 2GB여서 막히기도 했다.
(AWS S3는 5GB라서 다행히 업로드할 수 있었다.)
2. html -> 이미지 변환, PDF 생성, 프론트에서 API를 통한 스토리지 저장까지의 절차로 인해 속도가 느리다.
프론트에서 html을 이미지로 변환하고 인쇄 혹은 다운로드하는 시간은 체감상 3초 이상 걸린다.
이는 사용자에게도 좋지 않은 경험을 선사할 뿐만 아니라 일괄 다운로드를 위한 PDF 작업 시에도 시간을 엄청 잡아먹는 원인이었다.
3. 사용자의 브라우저 및 환경에 따라 PDF 결과물이 다르다.
프론트에서 작업하는 환경이었다 보니 각 사용자의 브라우저와 환경에 따라 결과물이 달라지기도 했다.
사내에서 일괄 작업 시에는 환경을 통일하면 해결되기는 하나 기관 사용자의 경우 간혹 이상하게 출력되는 경우가 발생했다.
대응을 위해 css로 width를 고정하고 인쇄 시마다 scrollTo({top: 0, left: 0})
을 해주었음에도 말이다.
(또한 이때까지는 Internet Explorer를 같이 대응했어야 해서 더 머리가 아팠다..)
이러한 문제를 해결하기 위해 아래 두 가지 방식으로 변경되었다.
- PDF 생성 시 이미지를 첨부하는 방식에서 html을 직접 PDF로 만들고, 인쇄 시에는 해당 PDF를 인쇄하도록 한다.
- 백엔드에서 일괄 작업 시에는 프론트 없이 자체적으로 PDF를 생성하여 저장한다.
이 시기에는 필자 말고도 프론트엔드 개발자분이 한 분 더 계셨고,
vue-html2pdf
라이브러리를 도입하시면서 해당 방식을 참고하여 인쇄 방식을 완전히 바꾸었다.
해당 라이브러리에서는 pageBreak 속성을 { mode: 'avoid-all' }
로 적용하면 html이 잘리는 경우 자동으로 다음 페이지로 넘어가도록 설정할 수 있다.
물론 같이 넘어갈 만큼에 맞춰 html을 잘 구성해야 한다.
이 방식으로 구성을 하니 이미지 작업이 없어 PDF가 즉각적으로 생성되었고,
해당 라이브러리에서 인쇄 기능까지 지원하여 위에서 발생한 인쇄 시 스타일 문제까지도 해결되었다.
브라우저 환경 및 속도 문제는 결국 백엔드에서 자체적으로 PDF를 생성해야 해결할 수 있다는 걸 알고 있었다.
다만 백엔드에서 html을 다루고 PDF를 생성하는 절차를 경험해 본 사람이 없었고 스타일이 잘 적용될지도 의문이었다.
프론트엔드 개발자분과 다른 백엔드 개발자분까지 붙어 테스트를 진행했고,
안 되는 css가 꽤 있지만 스타일을 적용하기에는 문제가 없다는 결론이 도출되어 해당 방식으로 진행했다.
각 서식에 대해 PDF 작업용 html, css를 새로 구성했고 iTextPDF
라이브러리를 통해 PDF를 생성했다.
각 변수들에 대해서는 html 코드 내에 {{var1}}
형식으로 넣어두고 각 변수명에 맞춰 replace 하는 형태로 작업되었다.
해당 작업 결과로 PDF의 평균 크기는 80KB 정도로 5배 감소했고,
중간 API를 통할 필요가 없어져 1초에 1개 이상의 PDF를 만드는 속도로 개선되었다.
사용자 및 PC 환경을 신경 쓸 필요 없다는 점도 너무 좋았다.
2023년 - thymeleaf 도입
2022년 변경사항으로 인해 대부분의 문제는 해결되었으나 아래와 같은 문제가 새로 발생했다.
- html이 백엔드 소스 코드 및 DB에 혼재되어 있어 html 코드 작업 및 유지보수하기 어렵다.
- 변수를 replace로 작업하다 보니 혹시 모를 문제가 발생할 수 있다.
- 테스트 시마다 PDF를 생성하고 파일을 열어 확인해야 한다.
이 시기에 개인적으로 주변에 개발에 입문하려는 사람들이 있었는데,
그분들에게 도움을 주기 위해 여러 가지로 공부하다 thymeleaf가 다시금 생각이 났다.
백엔드에서 html을 직접 생성하고 화면을 확인하고 싶다면 SSR 방식을 응용할 수 있겠다 싶어 thymeleaf 템플릿으로 PDF를 생성하는 방법에 대해 알아보았고,
좋은 방법이 있어 해당 방식으로 작업했다.
작업에 필요한 html, css는 thymeleaf 템플릿에 구성하고 변수는 thymeleaf 문법에 맞춰 적용하도록 변경했다.
또한 thymeleaf로 생성된 PDF를 확인할 수 있는 페이지를 하나 만들었다.
작업한 템플릿을 기반으로 PDF를 생성하고 PDF 확인 페이지에서 PDF를 연결하여 해당 URL에서 실시간으로 PDF 결과물을 확인하며 작업할 수 있도록 구성했다.
이렇게 변경하니 위의 문제점들을 다 보완할 수 있었고 추후 유지보수나 협업에도 용이한 구조로 구성할 수 있었다. (thymeleaf로 PDF를 생성하고, 확인하면서 작업하는 방법은 추후 포스팅할 예정이다.)
앞으로
마지막 개선점이 남았다.
프론트에서 만든 다운로드 및 인쇄를 위한 PDF와 백엔드에서 일괄 다운로드를 위해 작업한 PDF와 다를 수 있다는 점이 있는데,
프론트의 서식 변경점을 백엔드에서 따라가지 못했을 때 백엔드에서 PDF를 생성한다면 문제가 발생할 수 있기 때문에 추후 개선점으로 남겨두었다.
이 부분은 서식 확인 자체가 백엔드의 html, css를 통한 결과물 혹은 PDF로 이루어지지 않는다면 조금 더 신중히 고민할 필요가 있을 것 같다.
'Work' 카테고리의 다른 글
FK(외래 키)에 대한 개인적인 생각 (2) | 2024.02.14 |
---|---|
패스트캠퍼스 일할맛 2기 1:1 멘토링 및 개발세미나 후기 (2) | 2023.12.24 |
패스트캠퍼스 [초격차 패키지 : 한 번에 끝내는 AWS 기반 아키텍처 설계와 DevOps. Online.] 수강 후기 (0) | 2023.12.15 |