✅ 오늘 배운 것
오늘은 프론트와 백엔드에서 알람을 사용할 수 있도록, 특히 투두가 업데이트 되면 서버에서 클라이언트로 데이터를 업데이트 할 것을 백그라운드 알람으로 보낼 수 있도록 하려고 한다.
알림의 사용 용도는 크게 두 가지이다. 첫 번째는 하루에 주기적으로 일의 완성도를 체크하기 위해서 특정 시간에 보내는 알람, 두 번째는 여러 기기 간 동기화를 위해서 서버에서 변경 사항이 생겼을 때 해당 클라이언트로 보내는 알람이다. 두 번째의 경우는 알람이긴 하지만 백그라운드로 보내서 사용자가 직접 보진 못한다.
이를 위해선 백엔드 API에서는 Update, Post 등의 API가 호출되었을 때 변경에 해당되는 클라이언트에게 알림을 보낼 수 있겠다. 그런 식의 로직을 짜면 될 것 같다. 그렇다면 프론트에서는 해당 알림을 받아서 백그라운드 모드일 경우 API를 한번 더 호출해 주거나, 백그라운드 모드가 아닐 경우는 알람을 띄워주면 되겠다.
그런데 그러기 위해서는 우선 지금까지 작업사항을 반영 못 하고 있었던 프론트 관련 이슈인 SZ-215를 먼저 머지해야 했다. 왜냐하면 해당 브랜치에서 로그인이랑 액세스토큰 재발급 이슈를 다루고 있었기 때문이다. 혹시나 싶어서 dev 브랜치에 checkout을 해 봤더니 구글로그인 버튼이 동작하지 않고 있었다. 다른 팀원이 이 이슈를 잘 해결해두었다는 말은 들었는데 dev 브랜치에는 그 사항이 반영되지 않은 것 같았다.
그래서 SZ-215로 다시 가서 앱을 실행시켜 보았다. 분명 잘 해결했다고 생각했는데 여전히 뭔가 이상하다. 우선 투두 뷰 화면으로는 잘 이동을 하는데, 콘솔에는 401 에러도 같이 찍힌다. 구글로그인을 했는데도 말이다. 이 부분이 말끔히 해결되지 않으면 알람을 못 구현한다. 로그인 된 상태의 유저에게만 알람을 줄 수 있으니 테스트할 방법이 없다. 결국 해당 알람 이슈는 SZ-215에 dependency가 걸려버렸다. 그러므로 우선 SZ-215를 먼저 처리하자.
우선 앱을 종료시켰다가 다시 켜 보니 401 에러는 나오지 않았다. 대신에 다른 에러가 떴다.
에러 메시지에서 준 링크를 보니 '다른 컴포넌트를 렌더링하고 있을 때는 컴포넌트를 업데이트할 수 없다'는 에러를 고치기가 까다롭다는 사람들의 깃허브 이슈가 보였다. 해당 링크에서는 해결 방법을 얻지는 못했고, trace 로그 중 어디를 봐야 할지는 알 수 있었다. trace 로그가 정말 길어서 볼 생각을 못 하고 있었는데, 하나씩 읽어보다 보니 내가 작성한 컴포넌트가 눈에 들어왔다.
DailyTodo라는 내가 작성한 컴포넌트 안의 UI Kitten의 ListItem 컴포넌트가 있고, 그 안에 TouchableOpacity 컴포넌트가 있고, 해당 컴포넌트의 accessoryRight으로 준 값에 대해서 문제가 생긴 것 같았다.
즉 accessoryRight 안에 있는 컴포넌트나 뷰는 렌더링 도중에 호출되는데, 이때 상태 변화도 같이 일어나고 있는 것이었다. 그런데 렌더링 과정 도중에는 상태를 변경하면 안 되기 때문에 (당연하다. 화면을 그리고 있는데 중간에 상태가 변하면 안 되겠지) 이러한 오류가 나는 것이었다.
이를 해결하려면 상태를 변경하는 로직이 accessoryRight의 값으로 들어간 컴포넌트 안에 있으면 안 되고, 별도의 이벤트 핸들러나 함수에서 이 작업을 처리하도록 위임해야 한다고 한다.
기존의 코드는 다음과 같았는데, TouchableOpacity에서 onPress 속성값의 함수를 별도로 정의하는 대신 바로 정의해 버려서 함수가 그때마다 렌더링되는 문제가 있나? 싶었다.
해당 부분을 다음과 같이 별도의 함수로 분리해 주었다.
그랬더니 에러가 없어졌다!
그러다가 또 다른 에러를 찾았다...! 정확히는 콘솔에 에러는 안 찍히지만, 앱이 의도한 대로 동작하지 않아서 에러라고 생각했다. 문제 상황은 다음과 같다. 앱 화면이 있고 하위 투두를 생성할 수 있게 되어있는데, 문제는 하위 투두를 생성하려다가 다시 버튼이나 컴포넌트 바깥을 누르면 해당 하위 투두의 TextInput 컴포넌트가 사라져야 한다. 이 부분에서 상태 관리가 제대로 안 되고 있다고 느꼈다.
음 그래도 일단 중요한 것은 앱이 제대로 '콘솔 오류 없이' 실행되는 것이었다... 물론 이것도 당연히 출시 전까지는 고쳐야 할 에러다! 그런데 일단 알람 기능을 먼저 하고 이걸 해야 알람 기능 작업이 안 밀릴 것 같다는 생각이 들었다. 그래서 이 부분은 SZ-215에서 다루지 않고, 별도의 버그 이슈를 파서 처리하려고 한다.
그러면 이제 SZ-215에서 모든 작업들(투두 CRUD, 하위 투두 CRUD)이 잘 되는지 확인해 보면 되겠다. 모든 작업이 순탄하게 된다면 dev 브랜치로 PR을 날릴 수 있었겠지만... 아쉽게도 그러지 못했다.
우선 투두의 CRUD까지는 잘 되었다(이 기준은 콘솔 에러가 나지 않고, 화면에 의도한대로 잘 반영되었을 때 잘 된다고 판단했다). 문제는 하위 투두의 CRUD였다. 앱을 껐다 켜도 정보가 없는 걸 보면 API 요청이 제대로 보내지지 않은 것 같았다.
ECS 콘솔을 확인해보니 400 Bad Request가 뜨고 있었다. 뭔가 요청을 보내고는 있는데 형식이 잘못되어서 제대로 추가가 되지 않는 것 같았다.
그리고 현재 개발서버에서는 액세스토큰의 만료기한을 30분으로 설정해주고 있는데, 밥 먹고 오니까 액세스토큰이 만료되었는지 401 AxiosError가 났다. 원래대로라면 토큰 갱신 API를 자동으로 호출해 주어야 하는데, 이 부분이 아직 제대로 동작하지 않는 것 같다. 즉 현재 고쳐야 할 점은 두 가지다.
1. 하위 투두 API를 호출할 때 나는 400 Bad Request 에러 해결하기
2. 토큰 갱신 API를 자동으로 호출하여 401 AxiosError 해결하기
2번이 완료되어야 1번도 작업할 수 있기 때문에, 2번 -> 1번 순으로 해결해 보자.
현재 2번 로직은 Api라는 싱글톤 패턴 클래스에서 axios 인스턴스를 정의해두고, 해당 axios 인스턴스에 interceptor라는 기능을 추가해서 쓰고 있었다. 이 인터셉터가 제대로 동작을 하는지를 우선 봐야겠다. 다시 axios 공식문서를 봤더니 공식문서에서 제안하는 코드 형식과 현재 GPT의 도움을 받아 작성된 코드의 형식이 조금 달랐다. 공식문서가 더 신뢰성이 있으므로 공식문서대로 바꿔보았다.
1번 에러의 경우, 로그를 찍어보니 문제 상황을 알 수 있었다. 이전에 하위 투두 생성 API의 요청/응답 데이터를 조금 바뀌었던 게 생각났다. 현재는 객체, 딕셔너리 형태로 요청을 보내고 있었는데 지금은 리스트 형태로만 요청을 받도록 되어있었다. 이 부분은 API 수정도 필요하지만, 우선은 객체를 리스트로 한 겹 감싸서 보내는 게 해결이 더 빨라서 임시로 처리해 두었다.
그리고 프론트 앱에서 어떤 API들을 호출하고 있는지 로그를 찍어보았다. 토큰을 인증하는 API가 먼저 실행되는 줄 알았는데 카테고리를 불러오는 API가 먼저 실행되고 있었다. 이 부분은 수정되면 좋을 것 같았다. 아무튼 그 외에는 토큰을 인증하려고 시도하고, 투두와 같은 정보들을 불러오는 것으로 보였다.
문제는 여기서 401 에러가 났는데 토큰 갱신 API 로그는 없었다. 토큰 갱신 API는 request() 메소드를 다시 호출하지 않고, this.axiosInstance()를 호출하여 별도로 처리하기 때문이었다. 그러면 토큰 갱신 API도 잘 호출되고 있다는 것인데, 무슨 문제일까? 토큰 갱신 API에 요청을 보낼 때 body에 보내는 'this.refreshToken'의 값을 찍어보니 이유를 알 수 있었다. 해당 값이 'undefined'로 찍히고 있었다.
코드상에서는 해당 API 인스턴스의 생성자에서 init() 메소드를 실행하고 해당 메소드 안에서 AsyncStorage 안에 있는 accessToken, refreshToken의 값을 넣어주고 있었다. 해당 메소드에서 변수들의 값을 로그로 찍어보았을 때도 토큰 값이 있다고 찍혔었다.
알고보니 init() 메소드는 비동기적으로 실행되기 때문에 해당 메소드에서 refreshToken에 값을 할당하기 전에 이미 axios 인스턴스가 초기화 되었을 수 있다. 하지만 constructor() 생성자 메소드 안에서 바로 await 예약어를 사용할 수는 없기 때문에, then()이나 catch()로 비동기 함수가 반환하는 Promise를 추가로 처리하는 로직을 작성해줘야 하겠다.
사실 이 부분을 작업하다 에러가 났었다. .then()을 통해 로직을 실행해도 여전히 undefined로 값이 찍히는 거였다. 나는 짚이는 부분이 더 이상 없었어서, 혹시 멘토님은 어떤 실마리라도 아실 수 있겠다 싶어 도움을 요청드려 보았다. 알고보니 axios interceptor 로직 내부에서 정상 response 받는 부분을 function() 문법으로 처리했었는데, 화살표 함수로 바꾸니 문제가 풀렸다..! 정말 예상하지 못했고, 사실 왜 풀렸는지 아직 제대로 이해하지 못한 부분이기도 하다.
그래도 멘토님이 감사하게도 바로 에러의 원인을 짐작해주셔서 에러를 고칠 수 있었다. 새삼 대강 보이는 로직만이 전부가 아니고 문법 하나하나도 동작에 영향을 미치는구나 싶었다. JS에 대해서 모르는 게 많다는 걸 오늘도 하나 더 알아가본다...!
function 함수와 화살표 함수는 this 키워드의 동작 방식에서 차이가 있다고 한다. 멘토님도 'this binding 이슈 때문에 화살표함수를 많이 사용한다'고 하셨는데 이 부분과 관련된 내용 같았다. 사실 GPT가 풀어서 설명해준 내용을 100% 이해하진 못했는데, function으로 정의된 함수에서 this를 사용하면, 함수가 호출되는 위치에 따라서 해당 위치가 전역(global)인지, 지역(local)인지 등에 따라 this가 의미하는 대상이 달라질 수 있다고 이해했다. 반면 화살표함수로 정의하면 this는 해당 화살표 함수가 선언된 위치에서의 this와 동일해서, 어떤 위치에서 함수가 호출되건 this가 가리키는 대상은 불변한다고 한다.
✅ 궁금한 점
1. git rebase의 원리가 궁금하다. 이거 예전에도 남겼던 것 같은데 소스 코드를 보자!
2. 컴포넌트에서 setState가 호출될 때 공식문서에서는 'enqueue'라는 표현을 썼다. 컴포넌트들마다 일종의 큐가 있고, setState처럼 컴포넌트의 상태가 변경되어야 할 때 해당 정보가 큐에 들어가서 대기하는 방식으로 구현했나보다 싶어서 신기했다.
3. 화살표 함수와 function()이 그냥 보기에만 다른 게 아니었구나 싶다..! 멘토님 피드백을 보면서 알았다. 다음에 이 내용도 파 보자!
'개발 일기장 > SWM Onestep' 카테고리의 다른 글
20240828 TIL: 알람 기능 개발하기 [진행중] (0) | 2024.08.28 |
---|---|
20240827 TIL: 알람 기능 개발하려다가 다 못하고 홍보글 초안 작성하기 [진행중] (3) | 2024.08.28 |
20240825 TIL: 하위 투두 프롬프팅 시도하기 (0) | 2024.08.25 |
20240824 TIL: 파이썬 커맨드로 ECS 태스크 정의 json 파일에 동적으로 환경변수 값 넣기 & 서버 성능 향상 시도하기 (0) | 2024.08.24 |
20240823 TIL: uvicorn + gunicorn으로 서버 성능 향상시키고 locust로 테스트하기 & 파이썬 커맨드로 ECS 태스크 정의 json 파일에 동적으로 환경변수 값 넣기 [진행중] (0) | 2024.08.23 |