오늘 배운 것

오늘은 알림 기능 개발을 마저 진행해보려고 한다. 지금까지는 서버에서 알림을 보내면 앱의 화면에 알림이 잘 뜨는지를 확인했고, 이제는 잘 뜨는 것을 확인했으니 목적에 맞게 알람을 보내도록 코드를 작성해주면 되겠다. 

 

목적에 맞게 알람을 보내려면 크게 두 가지의 요구사항을 충족해야 한다.

1. foreground 알림의 경우, 아침(오전 8시), 점심(오후 2시), 저녁(오후 8시) 시간을 기본값으로 하고, 각 시간마다 사용자에게 알림을 보낸다. 

2. background 알림의 경우, 사용자가 CRUD 중 CUD에 해당하는 API를 호출한다면 사용자(User)에 해당하는, 현재 알림을 보낸 디바이스가 아닌 다른 디바이스(Device)가 있는지 확인하고, 있다면 해당 디바이스에게 API를 다시 호출하라는 알림을 보낸다. 

 

우선 첫 번째 경우부터 작업해 보자. 

 

지정된 시간마다 알림을 보내야 하니, 처음에 생각난 방법은 cronjob을 이용해서 서버에서 특정 시간마다 배치 로직을 돌려주는 것이었다. 'django cronjob'으로 검색해 보니 django-crontab이라는 라이브러리가 나왔다. 찾아보니 지속적으로 실행해주었으면 하는 Job에 해당하는 함수를 정의해준 뒤, settings.py에 해당 함수를 cronjob 날짜 표시 규칙에 맞게 정의해주면 끝이어서 간단해 보였다. 

 

우선 todos 앱 내부에 jobs.py라는 cronjob에 쓰일 job 함수들을 정의하는 용도의 파일을 만들어 주었다. 그리고 다음과 같이 send_day_alarm 이라는 공통 로직을 만들어 주었다. 

MORNING_ALARM_TITLE = "오늘의 할 일을 확인해보세요"
AFTERNOON_ALARM_TITLE = "지금 할 일을 확인해보세요"
EVENING_ALARM_TITLE = "오늘의 남은 할 일을 확인해보세요"


def send_morninig_alarm():
    send_day_alarm(MORNING_ALARM_TITLE)


def send_afternoon_alarm():
    send_day_alarm(AFTERNOON_ALARM_TITLE)


def send_evening_alarm():
    send_day_alarm(EVENING_ALARM_TITLE)


def send_day_alarm(alarm_title):
    users_prefetch = Prefetch('user__todo_set', queryset=Todo.objects.filter(is_completed=False))
    devices = FCMDevice.objects.all().select_related('user').prefetch_related(users_prefetch)
    try:
        for device in devices:
            todos_queryset = device.user.todo_set.filter(is_completed=False).values_list("content", flat=True)
            todos_list = "\n".join(todos_queryset)
            device.send_message(
                messaging.Message(
                    notification=messaging.Notification(
                        title=alarm_title,
                        body=todos_list,
                    ),
                )
            )
    except Exception:
        pass

 

FCMDevice는 fcm_django에서 send_message() 메소드를 통해 알람을 편하게 보낼 수 있게 하는 전용 모델이다. 이 FCMDevice와 User는 N:1 관계이고, User와 할 일을 나타내는 Todo 모델은 1:N 관계이다. 그리고 여기서 원하는 것은 FCMDevice를 조회하면서 관련 User와 Todo 데이터 모두를 한 번의 쿼리로 조회하는 것이었다. 

 

이를 위해서 django의 Prefetch를 사용했다. django의 select_related와 prefetch_related 안에 Prefetch() 객체를 넣어서 두 번의 join문을 통해 한 번의 쿼리로 User, Todo 데이터를 가진 FCMDevice 쿼리셋을 만들었다. 

 

궁금한 점

1. INSTALLED_APPS에 'django_crontab'을 넣으면 django에서 이를 어떻게 인식하는 것일까?

2. 'python manage.py crontab add' 명령어를 입력하면 settings.py에 입력한 함수 커맨드가 crontab에서 관리해야 할 job으로 등록되는 것으로 이해했다. 이 과정이 어떻게 일어나는지 궁금하다. 

3. Prefetch를 통해서 데이터를 불러오는 것과 그냥 objects.all()를 통해서 데이터를 불러오는 것은 차이가 있을까? 

4. select_related나 prefetch_related 안에 Prefetch() 객체를 넣으면 django에서 이를 어떻게 인식해서 join문으로 DB에 쿼리를 날리는지 궁금하다. 

 

 오늘 배운 것

오늘은 두 가지의 과제가 있다. 하나는 main workflow를 정상화해서 develop 브랜치의 내용이 main 브랜치로도 반영되도록 모종의 에러를 해결해야 한다. 또 다른 하나는 현재 develop 브랜치에서 settings 파일의 debug 변수를 True로 두고 있음에도 swagger 페이지가 제대로 표시되지 않는 오류(오류인지 정상 작동하는 것인지는 모르겠지만 우리 입장에서는 나타나야 하기 때문에 오류로 간주했다)가 있다. 

 

우선 첫 번째 업무부터 해 보자. main workflow가 과연 모종의 이유로 롤백되고 있는지를 먼저 확인하자. AWS ECS의 클러스터의 서비스 로그를 확인해봤는데 'rolled back'이라는 문구가 눈에 띄었다. 모종의 이유로 ECS가 롤백되고 있었다. 

 

CloudFormation에서 더 구체적인 로그를 보니, 이전에 개발서버에 있었던 거의 똑같은 이슈가 프로덕션 서버에 발생하고 있었다. 디렉토리명이 맞지 않아서 생긴 오류였다.

 

그래서 'onestep_be.settings.prod' 라는 모듈을 main 브랜치에서 사용하고 있는지를 확인해 보았다. 태스크 정의 JSON 파일에서 변수를 동적으로 넣어 주는 코드에서 해당 값을 넣어주고 있었다..!! 이 부분은 develop, main 브랜치 모두에서 수정되지 않은 부분이기 때문에 두 브랜치 모두 바꿔주어야 했다. 

 

해당 코드의 settings를 setting으로 바꿔 주고 다시 커밋을 올렸다. 다시 롤백이 발생하는지를 지켜봐야겠다. 우선 워크플로우는 무사히 잘 실행되었다. 

 

다행히 develop에서 새로 개발된 피쳐들이 프로덕션 서버에도 잘 적용되는 것을 확인할 수 있었다.


다음은 swagger view가 제대로 표시되지 않는 오류를 다뤄 보려고 한다. 찾아보니 이 부분은 크게 두 가지의 원인이 있을 수 있겠다. 첫 번째는 settings 파일의 DEBUG 변수 값에 따라서 swagger view를 표시하지 않도록 설정이 될 수 있다고 한다. 이 부분은 예상했던 부분이었다. 

 

그러나 DEBUG의 값이 False인 프로덕션과는 달리, DEBUG의 값이 True인 개발 서버에서도 swagger view가 표시되지 않으므로 이 문제만은 아닌 것 같았다. 

 

다른 가능성으로는, 현재 문제는 'python manage.py runserver' 명령어를 사용하다가 gunicorn 기반의 명령어를 사용하면서 swagger 페이지가 보이지 않게 된 것이므로 이 부분과 관련이 있을 수 있겠다. 찾아보니 python manage.py runserver로 서버를 띄울 때랑 gunicorn으로 서버를 띄울 때랑 정적 파일을 참조하는 방식이 다르다고 한다. 

 

그렇다면 gunicorn에서 어떻게 로컬 서버에 있는(사실 swagger 관련 파일이라 나는 이 파일들의 존재도 모르긴 했다. 어쨌든!) 정적 파일들을 서빙하게 할까? 집단지성의 힘을 빌리니 간단한 코드 몇 줄을 추가하면 된다고 한다. 

# onestep_be.urls
from django.contrib.staticfiles.urls import staticfiles_urlpatterns

urlpatterns += staticfiles_urlpatterns()

 

 궁금한 점

1. gunicorn은 원래는 어떻게 정적 파일을 서빙했을까

2. staticfiles_urlpatterns는 어떤 역할을 하길래 gunicorn이 python manage.py runserver와 똑같은 정적 파일을 서빙하도록 할 수 있는 걸까

 

 오늘 배운 것

오늘은 별도로 개발을 하지는 않았다. 현실적인 이유로 어쩔 수 없다고 생각하긴 하지만, 명색이 주니어 주니어 개발자인데 이래도 되나 싶어 조금 반성되는 하루였다. 일단 죄책감을 잠시 미뤄두고 내일까지 마감인 공고에 자기소개서를 썼다. 개발 직무가 사실 이력서를 중심으로 보지 자기소개를 써야 하는 기업은 많지 않아서, 간만에 레포트 과제를 하던 본전공 시절로 돌아간 것 같았다.

 

다행히 무사히 자소서를 제출했다. 이제는 다른 pdf 버전 자소서를 한번 더 셀프 첨삭하고, 멘토님들께 피드백을 받은 다음, 이틀 뒤 개발 컨퍼런스의 리쿠르팅 존에 가져가는 것이 목표이다. 

 

그럼에도 불구하고, 며칠간 안 하면 또 감이 떨어지고 개발과 낯을 가리게 되는 걸 알고는 있다. 그래서 내일은 조금이라도 꼭 개발을 해야 하겠다. 

 

그리고 여러 기업에 원서를 넣는 중이긴 한데 공채가 아니라 수시 채용인 곳들은 메일을 준다는 전제 하에 얼마 후에 메일을 주는지도 잘 모르겠다... 일단 체감상 2주 이상 연락이 없으면 알아서 서류 탈락이겠거니 하긴 하는데, 이게 맞으려나 모르겠다. 

취준 기록용으로 노션을 쓰고 있는데 정리가 잘 되어서 너무 잘 쓰고 있다... 노션 짱

 

암튼 내 멘탈도, 팀원들이랑 프로젝트도, 틈틈이 취준하면서 원서 넣는 것도 모두모두 파이팅이다. 

 

 오늘 배운 것

이제는 기존의 알림 코드를 조금 수정해서 어제 찾은 라이브러리를 사용하는 코드를 추가해 주면 된다. 다음과 같은 코드를 추가해 주었더니, 다음과 같은 경고 메시지가 떴다. 

import PushNotification from 'react-native-push-notification';

PushNotification.localNotification({
  title: remoteMessage.notification.title,
  message: remoteMessage.notification.body,
});

 

공식문서의 readME를 읽어보니 channelId 값이 없으면, 즉 channelId 값을 안 주거나 해당 channelId에 해당하는 채널이 없으면 notification이 trigger되지 않는다고 한다. 어쩐지 이렇게 채널 Id같은 추가 정보도 안 받고 알림이 보내지나 싶었다. (다시 생각해보면 FCM 라이브러리에서는 해당 문제가 없었던 이유가, 이미 FCM 토큰으로 특정 디바이스를 타겟팅해서 보내기 때문에 별도의 channel ID가 필요없던 것이라고 추측해본다.) 그리고 여러 가지 이유로 레포가 활발히 업데이트 되지 않는다며 Notifee와 같은 다른 라이브러리를 사용하기를 추천하기도 했다... 

 

사실 새 라이브러리를 배우는 것은 문제가 되지 않았으나, 현재 여러 팀원이 프론트 개발에 참여하고 있었기 때문에 안드로이드와 같은 RN 영역 외의 설정이 복잡하지 않는 라이브러리였으면 좋겠다고 생각했다. 그래서 Notifeereact-native-notifications, 그리고 현재 라이브러리인 react-native-push-notifications의 공식문서를 비교해 보면서 가장 안드로이드 설정이 간단한 라이브러리를 선택하기로 했다. 

 

자세히 보지는 않았지만, Notifee의 설치 및 셋업 가이드를 보니 매우 간단하고, 중간에 /android 폴더를 건드리는 일도 없었다. 그래서 일단은 Notifee 라이브러리를 선택했다. 설치 가이드를 따라서 진행했다. 뒷부분에는 Expo 설치 가이드도 있었는데, 그 부분은 시도해보니 expo에서 지원하는 valid plugin이 아니라는 에러가 나서 이후로 쭉 진행하지는 못했다. 

 

그런데 'npm run android:dev'(팀원이 scripts 값으로 별도로 설정한 명령어다)로 서버 prebuild를 시도하니 에러가 났다. 이 부분은 expo dependency(app.config.js에 있는 plugins 변수의 값)에서 @notifee/react-native 부분을 제거해서 오류를 임시로 해결하였다. 

 

그리고 'rnfirebase' 공식문서를 보는데 팀원이 빼먹은 부분을 알려주었다. 바로 유저의 permission을 받아야 알림을 표시할 수 있었는데, 그렇게 권한을 요청하는 코드를 빼먹었었다. 

import {PermissionsAndroid} from 'react-native';
  
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);

Allow

 

그런데 권한도 받았는데 알림이 여전히 오지 않았다. 그런데 rnfirebase 공식문서에서는 notification 속성이 메시지에 있고, 앱이 background 상태에 있다면 알림은 표시되는 것이 기본값이라고 말하고 있었다. 그래서 혹시 서버에서 알림을 보내는 코드가 잘못되었을 가능성은 없을까? 라는 의문이 들었다. 다시 fcm_django 공식문서를 봤다. 

 

이상한 점이 하나 있었는데, 나는 기존의 messaging.Message 인스턴스를 별도의 message 변수에 할당해서 사용하고 있었다. 그런데 공식문서에서는 전부 messaging.Message 인스턴스를 FCMDevice의 send_message 안에 직접 선언하고 있었다. 그러니까 이런 식이었다. 나는 아래와 같이 코드를 작성하고 있었다. 

from firebase_admin import credentials, messaging
from fcm_django.models import FCMDevice

def send_push_notification(token, title, body):
    message = messaging.Message(
        notification=messaging.Notification(
            title=title,
            body=body,
        ),
    )
    device = FCMDevice.objects.filter(registration_id=token).first()
    try:
        device.send_message(message)

 

그런데 위의 코드를 아래와 같이 바꿔주었더니 잘 동작하는 게 아닌가. message 변수로 할당된 값을 그대로 device.send_message() 안의 인자 값으로 넣어주었을 뿐인데 알람이 잘 들어왔다. 

def send_push_notification(token, title, body):
    device = FCMDevice.objects.filter(registration_id=token).first()
    try:
        device.send_message(
            messaging.Message(
                notification=messaging.Notification(
                    title=title,
                    body=body,
                ),
            )
        )

좋은데 쫌 어이가 없다

 

 궁금한 점

1. 위와 같은 알람 이슈는 왜 일어난 것일까

 

 오늘 배운 것

오늘도 마저 어제 하던 FCM 알람 기능을 적용하려고 한다. 어제 코드로 보았을 때는 백엔드 쪽에서는 알림을 잘 보내고 있는데 프론트엔드 쪽에서 그 알림을 화면에 띄워주는 부분이 되지 않는 것 같아서, 다시 공식문서를 보면서 내가 놓친 부분이 있는지를 보았다.

 

다시 보니 프론트에서 특히 앱이 foreground 상태일 때는 Notification이 아예 표시되지 않도록 되어 있었다. 그렇다면 알림은 잘 수신되는데 단순히 화면에서 나타나지 않는 게 아닐까 싶어서 onMessage() 메시지 핸들러 안에 로그를 찍어보았다. 그랬더니 로그가 찍히고 있었다. 

 

즉 메시지는 잘 도착했는데 Notification 타입의 메시지는 앱이 Foreground 상태일 때 표시되지 않는 것이 기본값이므로 표시되고 있지 않았던 거였다. 하지만 나는 앱이 Foreground 상태가 아닐 때 메시지가 표시되었으면 좋겠고 이를 테스트하고 싶었다. 그래서 앱을 잠시 백그라운드 상태로 만들고 다시 알림을 보내보았다. 그랬더니 메시지가 백그라운드에서 수신되었다는 알림 자체는 왔다. 다만 화면에 표시되지 않았다. 이는 Quit 상태일 때도 마찬가지였다. 

 

아마도 메시지 핸들러가 호출은 잘 되는데 별도로 알림을 띄우라는 코드가 없으니 안 띄우고 메시지 핸들러만 호출되는 것이라고 생각했다. 알림을 띄우는 것과 관련된 다른 라이브러리가 있길래 추가로 이 라이브러리를 사용하면 좋을 것 같았다. 

npm install --save react-native-push-notification

 

 오늘 배운 것

오늘은 우선 구글 플레이스토어 콘솔에서 배포가 잘 되고 있는지 확인했다. 다행히 아직까지는 오류가 난 것이 없어서, 당장 세 명이 뛰어들 만한 급한 이슈는 없다고 판단했다. 

 

어제 잘 돌아가는 것을 확인한 서버를 일단 켜 두자. 프론트엔드와 백엔드 서버 모두 켜 두었다. 

 

일단 디자인은 잠시 미뤄둔 상황이다. 그리고 구글 플레이스토어의 앱 배포도 아직까지는 문제가 없다. 물론 다운로드 수가 20을 넘어야 하지만, 그건 차차 사용자 수를 모으면 되는 상황이므로 현재의 문제는 아니다. 그렇다면 이 상황에서 뭘 우선으로 해야 할까? 일단 앱이 배포된 다음에는, 사용자를 모으는 것이 우선이다. 사용자를 모으려면 우선은 앱이 정상 작동을 해야 하고, 고도화 기능(알람과 하위투두 고도화)이 필요하며, 틈틈이 홍보도 해야한다. 

 

논의 끝에 팀원 한 명은 앱의 정상 작동을 위해 버그를 고치는 일, 다른 한 명은 하위 투두 고도화를 위해 DB 및 모델을 설계하는 일, 그리고 나는 알람을 맡았다. 계속 미뤄지게 된 알람 이슈를 작업하게 된 것이다. 

 

사실 이미 작성된 로직이 있었어서, 해당 로직이 잘 작동하는지 확인을 하면 되었다. 문제는 결과값 자체는 잘 보내진다고 나오는데 에뮬레이터 기기에 알림이 오지 않는다는 점이었다. 

 

원인을 추측해 보자면 해당 토큰값이 에뮬레이터의 FCM 토큰값이 아니거나, 코드에 있는 Messaging 또는 Notification 객체가 제대로 동작하지 않았을 수 있겠다. 

 

우선 첫 번째 추측의 경우, 프론트에서는 다음과 같은 코드로 기기의 FCM 토큰을 가져오고 있었다. 이는 RN 공식문서에도 나와있는 방법이므로 이게 틀릴 가능성이 꽤 낮았다. (사실 만약 틀렸다고 하면, 깃헙 이슈를 보면서 어떻게 해야 하는지를 또 일일이 찾아봐야 해서 그게 아니길 바란다.)

import messaging from '@react-native-firebase/messaging';
const token = await messaging().getToken();

 

... 라고 생각했었는데, 알고보니 프론트에서 다른 브랜치로 작업을 하고 있었다. 즉 백엔드에서 맞는 기기로 알림을 보내도 프론트엔드에서 그 메시지를 받아서 표시해주는 로직이 동작해야 화면에 알림이 표시되는데, 그 로직을 작성해놓은 브랜치가 아니었던 것이다. git checkout과 git rebase(앱이 동작하지 않는 문제가 dev 브랜치에서 고쳐진 상태였기 때문에 rebase도 같이 했다)을 하고 다시 시도해 보았다. 여전히 되지 않았다. 

 

백엔드에서는 로직 중간에 오류가 없는 것으로 보아 프론트엔드의 알림을 받는 부분에 문제가 있을 것이라고 추측했다. 짚이는 부분 하나는 프론트에 현재 알림이 오면 그걸 그대로 화면에 나타내주는 코드가 없다는 거였다. 또한 헷갈렸던 부분은, 메시지를 보내거나 받을 때 디바이스의 FCM 토큰만 알면 별도로 채널 등의 정보를 따로 지정하지 않아도 무사히 해당 FCM 토큰을 가진 디바이스에서 메시지를 받을 수 있는지 의문이 들었다. 

 

다시 RN firebase 공식문서fcm django 공식문서를 보자. 공식문서를 보면서 여러 문제점을 찾았다. 첫 번째는 fcm-django에서 메시지를 보내려면 fcm-django에서 정의한 FCMDevice라는 모델을 사용해야 한다. 정확히는 해당 모델의 send_message 라는 메소드를 통해서 메시지가 보내진다. 그런데 그 부분이 코드에서 빠져 있었다. 아마도 디바이스 토큰 값은 정확했을 텐데 메시지가 제대로 전송되지 않은 것 같았다. 

 

그러면 이전에 임의로 정의해두었던 Device 모델 대신에 FCMDevice 모델을 사용해야 한다. 이를 위해서 해당 코드가 있는 구글로그인 로직의 일부를 바꿔주었다.

 

기존의 Device 모델에서 get_or_create 메소드를 통해 device_token(앞서 프론트에서 보내는 device token 값)의 값을 가진 Device 인스턴스가 있으면 이를 불러오고 없으면 만들어 주었다. 이번에도 마찬가지로 FCMDevice에서 get_or_create 메소드를 사용하려고 했는데, 그러려면 device_token 값을 어떤 필드에 저장해야 할지를 알아야 했다. 

 

fcm_django.models에 관련 모델들이 모두 정의되어 있었다. 우리가 사용하려는 FCMDevice는 AbstractFCMDevice를, AbstractFCMDevice 모델은 Device 모델을 상속받고 있었다. 그래서 이 FCM 토큰 값을 어디다 넣으라는 건가 싶었는데, 공식문서에 친절히 registration_id 필드에 넣으라고 나와있었다.

 

기존의 Device 모델에서 FCMDevice 모델로, 그리고 일부 필드값만 바꿔주었다. 

FCMDevice.objects.get_or_create(
    user=user, registration_id=device_token
)

 

그리고 'python manage.py showmigrations' 명령어로 확인해 보니 fcm_django 라이브러리와 관련된 모델 변경 사항이 아직 마이그레이션에 반영되지 않은 상태였다. 이것도 반영해 주었다. 

 

이렇게 기본 로직을 변경하고, 다시 프론트 에뮬레이터를 켜서 구글로그인을 한 번 실행해 주었다. 그래야 FCMDevice 모델에 해당 에뮬레이터의 토큰 값이 들어가기 때문이다. 그리고 나서 기존의 메시지를 보내는 로직을 다시 바꿔주었다. 

def send_push_notification(token, title, body):
    message = messaging.Message(
        notification=messaging.Notification(
            title=title,
            body=body,
        ),
    )
    device = FCMDevice.objects.filter(registration_id=token).first()
    try:
        device.send_message(message)
    except Exception as e:
        # sentry capture exception
        return PUSH_NOTIFICATION_ERROR
    return PUSH_NOTIFICATION_SUCCESS

 

이렇게 바꿔 주었고 메시지를 보내는 로직을 실행시키니 success라고 떴다. 그런데 여전히 에뮬레이터에서는 알림이 표시되지 않아서, 이번에는 프론트 쪽에 문제가 있을 가능성을 두고 프론트 코드를 고쳐봐야 할 것 같다. 

 

 궁금한 점

1. kafka-python 등의 kafka를 python에서 사용하기 위한 라이브러리가 있는 걸로 알고 있다. 문득 우리 서비스에서 kafka-python과 같은 라이브러리가 필요할 일이 있을지 궁금해졌다. 

2. FCMDevice의 send_message 로직은 어떻게 되어 있을까?

 

 오늘의 러닝 인증

 

 오늘 배운 것

오늘 오후 2시까지는 팀원들과 논의해서 새 앱 디자인을 정하고, 이후 개인 시간에는 FCM 알림을 붙여보려고 한다. 2시부터 4시까지는 소마 메이커스 특강이 있었고, 원래는 2시에 AI가 추천해줄 하위 투두를 어떻게 보여줄지에 대한 디자인은 정하지 못했었다. 그런데 너무 고맙게도 팀원들이 특강을 듣고 있는 동안 해당 디자인에 대해서 정하고 논의를 해 줘서, 이제 새 앱 디자인은 거의 정해졌다고 봐도 무방하겠다. 

 

남은 것은 FCM 알림이다. 오늘 '인프런이 성장하면서 변화해온 아키텍처'가 강연 주제였는데, 인프런이 조직 및 비즈니스 상황 변화에 따라 어떤 결정을 했는지도 알 수 있었지만 중요한 인사이트는 '회사의 비즈니스나 조직 상황에 맞는 적정 기술을 추구하는 것'이라는 생각이 들었다. 그리고 회사에서도 현재 자신이 어떤 상황인지(어떤 리소스가 얼마나 있는지)를 알고 그에 맞는 결정을 내리는, 즉 적정 기술을 추구하는 개발자를 조직에서도 당연히 선호하겠다는 생각을 했다. 발상의 전환을 준 강연이었다. 

 

아무튼! 그래서 원래는 FCM 알림을 백그라운드 푸시알림으로 구현하는 것에 대해서 원래는 이게 트래픽이 많아지면 비효율적인 방법이 아닐까, 아니면 원래 알림과는 맞지 않는 취지 아닐까(실제로 그렇기는 하다)... 등등 여러 생각이 들었었다. 그런데 그러면 그건 그때 가서 고민하면 되는 게 아닐까? 라는 방향으로 생각이 바뀌었다. 일단 구현해보자. 

 

우선 구현을 하려면 프론트엔드의 앱이 제대로 동작해야 했다. develop 브랜치로 체크아웃한 뒤 한번 확인해봤다. 어떠한 이유인지는 모르겠지만, 프론트에서 로컬 서버를 호출하고 있는 것으로 보였다. 

 

이 문제도 해결되어야 하긴 한데, FCM 알람과 직접적인 연관은 없는 문제이므로 우선 백엔드도 로컬 서버를 띄워두기로 했다. 이번에는 또다른 에러가 났다. 전혀 상관 없는 에러인데, 문제는 이걸 해결하지 않으면 알림 테스트를 못 한다는 것이었다...! 그래서 해결해야 하는 상황이다. 아니면 다른 버그 이슈를 하나 파고 해당 이슈에 dependency를 걸어두는 것이 낫겠다. 즉 원래는 FCM 알림 이슈 해결 후 커스텀을 하려는 계획이었는데, 버그 해결 후 FCM 알림 이슈 후 커스텀을 하게 되었다. 

 

알고보니 이 문제는 다른 브랜치에서 해결 중이었는데 머지가 되지 않은 상황이었다. 해당 브랜치를 머지한 다음 .gitignore 파일에 올리지 않은 파일을 따로 Firebase에서 받아서 올려두니 잘 동작하였다. 

 

궁금한 점

 

 오늘 배운 것

어제 나름의 시도를 했는데 아직 서버를 HTTPS로 띄우지 못해서, 추가적으로 무엇을 더 할 수 있을지를 알아보았다. 두 가지로 추가로 체크할 사항이 있었다. 첫 번째는 SNI 설정을 통해 하나의 서버가 여러 도메인에 대해 여러 개의 인증서를 제공하도록 해야 했다. 왜냐하면 현재 서브도메인으로 등록된 개발 서버는 HTTPS로 잘 연결되는 상황인데 프로덕션 서버만 HTTP로 연결되는 상황이라, 서버에서 각 도메인에 맞는 인증서를 제공해야 했다. 

 

이를 확인하려면 로드밸런서에 SNI 설정이 잘 되어있는지를 확인해야 했다. 즉 'SNI용 리스너 인증서'가 설정되었는지를 확인했다. 어제 등록한 인증서가 나와 있었다. 

 

두 번째는 ACM 인증서가 현재 도메인을 지원하고 있는지였다. 사용하고 있는 ACM 인증서에 가서 '도메인' 쪽을 보았더니, 앞에 와일드카드(*)가 붙은 도메인을 전부 지원하고 있었다. 

 

그렇다면 다시 문제의 원인을 생각해 보자. 문제가 있을 만한 부분은 Route53, ACM 인증서, ELB의 리스너 등이다. 그런데 서브도메인은 잘 연결되고 그냥 도메인이 잘 연결되지 않는다면, 둘 다 공통적으로 사용하고 있는 ACM 인증서에는 문제가 없을 확률이 높다. 즉 Route53과 ELB의 리스너에 문제가 있을 확률이 높다. 

 

우선 ELB의 리스너 쪽을 보았다. 개발서버의 ELB 리스너와, 프로덕션 서버의 ELB 리스너를 보고 하나씩 설정을 비교해보기로 했다. '보안 정책'의 필드 부분에서 다른 부분이 있었다. 

개발 서버
프로덕션 서버

프로덕션 서버에서 사용하는 설정을 개발 서버와 똑같이 맞춰주었다. 프로덕션 로드밸런서의 보안 그룹도 개발 로드밸런서의 보안 그룹과 동일한 것으로 바꿔 주었다. 여전히 되지 않았다. 

 

그랬는데 팀원들과 얘기하던 와중 프로덕션 서버의 HTTPS 리스너에서 사용하는 인증서에는 인증서에 와일드카드(*)가 포함되어 있었는데, *.domain.com은 domain.com을 포함하지 않냐는 얘기가 나왔다. 찾아보니 *.domain.com은 domain.com을 포함하지 않았다! 즉 지금까지 ACM에서 사용하던 SSL/TLS 인증서는 전부 서브도메인이 있는 URL에서만 유효한 인증서였던 것이다. 

 

그래서 새로 ACM에서 domain.com에 해당하는 인증서를 만들어주고, 해당 인증서를 HTTPS 리스너에 할당해 주었더니 문제가 바로 해결되었다!! 


이제 안심하고 context switching을 해 보자. 다음은 디자인을 새로 해야 한다. 

 

+ Recent posts