Hello & Intro
안녕하세요,
볼드나인 프론트엔드 개발자 김용재 입니다.
오늘은 새로운 버전이 배포될 때 github API를 활용하여 사용자가 접속한 사이트를 새 버전으로 리로드 해주는 Check Version & Reload 기능을 소개해 드립니다.
Problem
사용자가 웹 서비스를 이용할 때 버그를 마주할 때가 있는데요.
저희는 SPA(Single Page Application) 방식의 웹 서비스를 제공하고 있기 때문에
매주 새 버전을 배포할 때마다 주기적으로 발생했던 문제가 있었습니다.
(SPA를 간단히 설명드리자면, 서버로부터 웹 사이트를 그려낼 수 있는 모든 데이터들을 다운 받아 화면에 그려주는 방식이라고 할 수 있습니다. 인터넷을 열고 사이트에 접속할 때나 새로고침을 할 때 이 데이터들을 받아옵니다.)
유저가 사용하는 화면과 관련된 코드(프론트엔드 코드)와 데이터를 유저에게 보내주는 서버와 관련된 코드(백엔드 코드)가 새로운 버전으로 업데이트가 되었는데, 유저가 인터넷을 새로 열어 접속하지 않거나, 새로고침을 하지 않으면 어떻게 될까요? 서버는 새로운 코드로 서비스를 제공할 준비가 되었는데 말이죠. 이전 버전의 프론트엔드 코드로 새로운 버전의 백엔드 코드에 요청을 보내니, 에러가 발생하지 않을 수 없습니다. 배포 다음날마다 사이트를 다시 열거나 새로고침을 하라고 공지하여 사용자에게 불편함을 줄 수도 없는 노릇입니다.
이 문제를 해결하기 위해서는 새 버전이 배포될 때 이를 확인하여 사용자의 브라우저를 새로고침 해주면 되는데요. 단, 사용자의 경험적인 측면을 고려해야 합니다. 중요한 작업을 하고 있는데, 갑자기 새로고침이 된다면 안 되겠죠.
가장 이상적인 해결 방법을 찾기 위해 프론트엔드 팀원들과 의견을 나눈 결과, 3가지 상황에서 새로고침을 해준다면 사용자도 자연스럽게 받아들일 것이라 판단을 내렸습니다.
첫째, 로그인하여 진입한 경우,
둘째, 페이지 이동한 경우,
마지막, 로그인 시간이 만료된 경우
입니다.
How to make
이 버전 리로드 기능을 구현하기 위해 많은 사전조사와 시도들이 있었으나, 현재 적용한 방법만 담백하게 말씀드리겠습니다.
바로 버전 관리 툴인 Github(깃헙) 의 Client API를 사용하는 방법입니다.
먼저 간단히 설명드리면, 새로운 버전을 배포할 때 깃헙의 액션이 돌아가는데, 혹시 그거 아셨나요?
각 깃헙 액션은 고유의 아이디(workflow Id) 와 배포 번호(run number)를 가지고 있다는 것입니다.
아래 이미지에서 238 보이시죠?? 배포 번호입니다.
즉, 어떤 깃 액션이냐, 예를 들어 테스트를 위한 배포인지 또는 실 서비스를 위한 배포인지를 구분할 수 있고, 배포 번호를 비교하여 새로운 버전인지를 알 수 있다는 것입니다.
바로 코드를 보겠습니다. checkVersionAndReload라는 함수를 원하는 상황에 부르면, 해당 환경을 확인하고 버전을 비교한 후 새로고침 해줍니다. 저희는 위에서 말씀드린 대로, 로그인 진입, 페이지 이동, 로그인 시간 만료 시 이 함수를 실행시킵니다. 코드를 따라 내려가며 주석으로 설명드리겠습니다.
//checkVersionAndReload.ts
// 아래의 환경변수는 깃 액션을 실행할 때 넣어줍니다.
const { GH_TOKEN, DEPLOY_ENV } =
import.meta.env ?? {};
const checkVersionAndReload = () => {
const targetDeployEnvs = ['프로덕션환경', '테스트1환경', '테스트2환경'];
// 프로덕션, 테스트1환경, 테스트2환경. 총 3가지의 배포 환경이 있다고 하면,
// 해당하는 환경이 버전 리로드가 필요한 환경인지 먼저 확인합니다.
const isProperDeployEnv = targetDeployEnvs.includes(DEPLOY_ENV);
if (!!GH_TOKEN && isProperDeployEnv) {
// 여기서 cdn 을 사용하여 octokit(client github api) 를 불러옵니다.
// 모듈을 설치하지 않고 사용할 수 있기에 부담이 없지요.
// ref: https://www.skypack.dev/
import('https://cdn.skypack.dev/octokit').then(async (res) => {
// 여기에서 깃헙 토큰을 이용, 레포지토리 정보를 가져옵니다.
const octokit = new res.Octokit({
auth: GH_TOKEN,
});
// 말그대로, 새로고침할 타겟을 여기에서 찾는데요,
// 환경을 먼저 비교하고 그에 해당하는 워크플로우 아이디와
// 로컬스토리지에 저장할 액션이름을 설정합니다. { 액션이름: 액션번호(버전번호) } 이렇게 저장합니다.
const reloadTarget =
DEPLOY_ENV === targetDeployEnvs[0]
? {
workflowId: '프로덕션 워크플로우 아이디',
keyName: '프로덕션 액션이름',
}
: DEPLOY_ENV === targetDeployEnvs[1]
? {
workflowId: '테스트1환경',
keyName: '테스트 첫번째환경 액션이름',
}
: {
workflowId: '테스트2환경',
keyName: '테스트 두번째환경 액션이름',
};
// 완료된 워크플로우의 가장 최신의 데이터를 가져옵니다.
const gitActionData = await octokit.request(
'GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs',
{
owner: '레포 오너 이름',
repo: '레포 이름',
workflow_id: reloadTarget.workflowId,
per_page: 1,
page: 1,
status: 'completed',
}
);
// 가장 최신의 액션번호 입니다. 물론, 무작위 문자로 이루어진 고유한 액션id 라는 것도 있지만,
// 깃헙 사이트의 깃 액션 페이지에서 액션 번호를 쉽게 찾아 비교할 수 있기에 이를 사용했습니다.
const newActionNumber = await gitActionData.data.workflow_runs[0]
.run_number;
const lastActionNumber = JSON.parse(
localStorage.getItem(reloadTarget.keyName) || 'null'
);
// 로컬 스토리지에 저장된 액션 번호가 없거나, 상이하다면 리로드를 해줍니다.
if (
!lastActionNumber ||
newActionNumber !== lastActionNumber
) {
localStorage.setItem(
reloadTarget.keyName,
JSON.stringify(newActionNumber)
);
location.reload();
}
});
}
};
export { checkVersionAndReload };
TypeScript
복사
아래 코드는 타입스크립트 타입 에러를 방지하기 위한 코드입니다. 참고해 주세요.
declare global {
interface Window {
checkVersionReload: () => Promise<void>;
}
}
TypeScript
복사
Conclusion & Bye
네, 이것으로 새 버전 배포에 따른 에러를 해결했습니다.
제가 작성한 코드를 돌아보니, 새록새록 그때의 감정이 느껴지는 것 같네요.
물론, 제가 설명드린 방법이 최선은 아닐 것입니다. 당연히 개선점이 많을 것이고요.
각 상황에 맞게 최적의 방법을 찾는 것이 가장 중요한 것 같습니다.
이 글이 누군가에게 조금이라도 도움이 되길 바랍니다.
긴 글 읽어주셔서 감사합니다.
다음에 봬요