들어가며
스타트업에서 서버 개발자로 일하면서, 푸시 알림에 대해 처음 개발을 시작했습니다. 푸시 알림을 어떻게 보내야 하는지도 전혀 몰랐기에, 푸시 알림을 보내는 법, 푸시 알림을 반복해서 보내는 법 등을 잘 설계해야만 했습니다. 그 과정에서 수많은 삽질을 반복했습니다. 이 글은 저처럼 삽질을 하지 않기를 바라며 작성한 글입니다. 제 글을 통해 푸시 알림을 잘 설계할 수 있는 개발자가 되길 바랍니다.
Firebase 활용하기
푸시 알림을 활용하기 위해서는 먼저 Firebase를 알아야 합니다. 푸시 알림을 보내려면 Firebase를 통해 보내야 하기 때문입니다. Firebase를 활용해서 푸시 알림을 보내는 방법은 간단합니다. 먼저 앱 서버에서 FCM 서버로 HTTP Request를 post 방식으로 요청하면 FCM 서버에서 HTTP Response 응답이 옵니다. 그리고 FCM 서버에서 앱으로 메시지를 보냅니다. 앱에서는 받은 메시지를 처리하고 응답 메시지를 보냅니다.
만약 앱 서버에서 FCM 서버와 소통하려면, 어떻게 소통할지에 대한 방법을 정해야 합니다. 이를 위해 Firebase Admin SDK 또는 원시 프로토콜을 사용할 수 있습니다.
Firebase Admin SDK를 사용하면 크게 4가지 기능을 사용할 수 있습니다.
1. 개별 기기에 메시지 보내기
2. 주제 및 하나 이상의 일치하는 조건문에 메시지 보내기
3. 기기에서 주제 구독 및 구독 취소
4. 다양한 타겟 플랫폼에 맞는 메시지 페이로드 구성
저는 Node.js를 활용하기 때문에, Firebase Admin SDK로 Node.js를 사용했습니다.
Firebase로 메시지 보내기
Firebase의 FCM 서버를 이용해 Android, iOS에 푸시 알림을 보낼 수 있는 간단한 서버를 만들어보겠습니다.
1. Firebase Admin SDK 설치
먼저 Firebase Admin SDK를 사용하기 위해서는 npm으로 firebase-admin을 설치해야 합니다.
$ npm install firebase-admin --save
firebase-admin이 package.json에 설치가 됐다면, 다음으로 넘어가 봅시다.
2. 서버 키 다운로드
SDK를 초기화해주려면 서버 키가 필요합니다. firebase 콘솔 화면으로 들어가서
'설정 아이콘 -> 프로젝트 설정 -> 서비스 계정 -> 새 비공개 키 생성'으로 들어갑니다.
이런 과정을 거쳐 특정 키를 생성하고, 생성된 키를 노출되지 않게끔 잘 관리해줘야 합니다. 그리고 SDK를 초기화하는 코드를 입력해야 합니다.
3. SDK 초기화
const admin = require('firebase-admin')
let serviceAccount = require('../(서버 키 경로).json')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
})
serviceAccount 변수에 fcm에서 비공개 키를 저장한 파일의 경로를 작성합니다. 또한 아래의 admin.initailizeApp을 설정해줍니다.
4. Push 메시지 보내기
위의 경우처럼 설정한다면, 푸시 알림을 보낼 준비가 된 것입니다.
만약 아래의 코드를 작성하고 push 라우트에 요청을 보내면, target_token을 가진 앱에 푸시 알림이 가는 것을 살펴볼 수 있을 것입니다.
// push PAGE
router.get('/push', (req, res, next) => {
let target_token =
'e2k1ZL3cet-NAHp3gz_wB:BZX12cw23HGnGqSJQLGrazzasdca3tq0JkPKJlTY5cHmylMiR8dAdGAdKi9o_rf9y55H1mmvvAgHj0ZKjZyk23Q_trNrmgQx1A6h3LaoADdlPV-kX5czoDnL1F-gc2DOZJucEmf4To6hje4AfHl'
let message = {
data: {
title: '푸시알림 테스트',
body: '푸시알림 테스트합니다.',
style: '테스트',
},
token: target_token,
}
admin
.messaging()
.send(message)
.then(function (response) {
console.log('Successfully sent message: : ', response)
})
.catch(function (err) {
console.log('Error Sending message!!! : ', err)
})
})
지금까지는 firebase로 푸시 알림을 어떻게 보낼 수 있을지를 알아봤습니다. 그렇다면, 푸시 알림을 반복적으로 보내고 싶을 때가 있을 텐데, 그럴 때는 어떻게 푸시 알림을 보낼 수 있을까요? 반복해서 푸시 알림을 보낼 수 있는 방법에 대해 알아보겠습니다.
Node Schedule을 활용한 푸시 알림 구현
특정 시간에 이벤트를 발생시키는 모듈이 Node schedule입니다. 매일 9시에 고객들에게 특정 푸시 알림을 보낸다고 가정한다면, 반복되는 작업을 처리할 때 사용할 수 있습니다. 자바스크립트에서 반복적으로 이벤트를 발생시키기 위해서는 setInterval()를 사용하는 방법도 있지만, 정확한 시간에 이벤트를 발생시키려고 한다면, node-schedule 모듈을 사용해야 합니다. 그렇다면, node-schedule을 어떻게 사용해야 할까요? 이에 대해 알아보겠습니다.
1. Node Schedule 설치
먼저 Node Schedule을 사용하기 위해서는 npm으로 node-schedule을 설치해야 합니다.
$ npm install node-schedule
만약 설치가 됐다면, 어떻게 사용해야 할지에 대해 살펴보겠습니다.
2. Cron 표현식
이 모듈은 Cron 표현식으로 시간을 지정할 수 있습니다.
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, OPTIONAL)
3. 특수문자 의미
Cron 표현식에서 사용되는 특수 문자들이 있습니다. 순서대로 살펴보겠습니다.
1. '*' -> 모든 값을 의미합니다.
2. ',' -> 쉼표는 한 항목에 여러 목록을 입력할 때 사용됩니다.
예를 들어, 5 번째 필드 (요일)에 “MON, WED, FRI”를 사용하면 월요일, 수요일, 금요일을 의미합니다.
3. '-' -> 하이폰은 범위를 정의합니다. 예를 들어 2000-2010 년은 2000 년에서 2010 년 사이의 모든 연도를 나타냅니다.
4. '%' -> 명령에서 백분율 기호 (%)는 백 슬래시 ()로 이스케이프하지 않는 한 개행 문자로 변경되고
첫 번째 % 이후의 모든 데이터는 표준 입력으로 명령에 전송됩니다.
4. 비표준 문자
다음은 비표준 문자이며 일부 cron 표현에만 사용할 수 있습니다. node-schedule에서는 W, L, # 은 지원하지 않습니다.
1. 'L' -> ‘L’은 ‘마지막(Last)’을 의미합니다.
day-of-week 필드에서 사용하면 주어진 달의 “지난 금요일”( “5L”)과 같은 구문을 지정할 수 있습니다.
day-of-month 필드에서는 해당 월의 마지막 날을 지정합니다.
2. 'W' -> W문자는 day-of-month 필드에 사용할 수 있습니다.
이 문자는 주어진 요일에 가장 가까운 평일 (월 - 금)을 지정하는 데 사용합니다.
예를 들어, “15W”를 day-of-month 필드의 값으로 지정하면 의미는 “매월 15 일 가장 가까운 평일” 입니다.
그래서 15 일이 토요일이라면 14 일 금요일에 이벤트가 발생합니다. 15 일이 일요일이면 16 일 월요일에 발생합니다.
15 일이 화요일이면 15 일 화요일에 시작합니다.
그러나 달의 값으로 “1W”를 지정하고 1 일이 토요일이면 월의 경계는 넘지 않기 때문에,
3일 월요일에 실행됩니다. W문자는 하루 일만 지정할 수 있고, 목록이나 범위에서는 사용할 수 없습니다.
3. '#' -> #은 요일 필드에 사용할 수 있으며 1에서 5 사이의 숫자가 와야합니다.
주어진 달의 “두 번째 금요일”과 같은 구문을 지정할 수 있습니다.
예를 들어, 요일 필드에 “5 # 3”을 입력하면 매월 셋째 금요일이 됩니다.
4. '?' -> *대신에 day-of-month 또는 day-of-week 중 하나를 공백으로 남겨 둘 때 사용합니다.
어떤 곳에서는 cron 데몬의 시동 시간으로 정의됩니다.
그래서 cron이 오전 8시 25 분에 시작되면 ? ? * * * * 가 25 8 * * * *로 업데이트되고
다시 시작할 때까지 매일 이 시간에 실행됩니다.
5. '/' -> 슬래시를 간격과 결합하여 간격 값을 지정할 수 있습니다.
예를 들어, 분 필드의 * / 5 는 5 분마다 나타냅니다.
5. 간단한 예제
1) 매 10초마다 실행되는 스케줄을 설정하는 방법
const schedule = require('node-schedule');
const j = schedule.scheduleJob('10 * * * *', function(){
console.log('매 10초에 실행');
});
2) 일, 목, 금, 토 중 실행 날짜 17시 0분에 콘솔에 값을 출력하는 방법
var j = schedule.scheduleJob('0 17 ? * 0,4-6', function(){
console.log('일, 목, 금, 토 중 실행 날짜 17시 0분에 실행');
});
3) Cron 기법을 사용하지 않고, Date()를 사용해 지정하는 방법
var schedule = require('node-schedule');
var date = new Date(2021, 07, 30, 5, 30, 0);
var j = schedule.scheduleJob(date, function(){
console.log('반복적인 콘솔 출력을 해보자');
});
Node Schedule을 활용한 푸시 알림의 한계
만약 node schedule로 예정된 시간에 푸시 알림을 보내려고 했는데, 서버가 멈춰버리거나 꺼진다면 어떻게 될까요? 결론적으로 node schedule로 예약을 했던 푸시 알림이 보내지지 않습니다. 푸시 알림을 보내야 한다는 기억을 서버가 해야 하는데, 서버가 꺼진다면, 모든 기억이 없어져버리는 것이기 때문에, 푸시 알림을 보내기로 했던 것이 가지 않는 상황이 발생합니다.
이런 문제를 해결하려면 어떻게 해야 할까요? 이 문제를 해결하기 위한 방법이 많겠지만, 저는 Agenda를 활용해서 이 문제를 해결했습니다. 어떻게 Agenda를 활용해서 푸시 알림을 효율적으로 보낼 수 있을까요? 이에 대해 알아보겠습니다.
MongoDB 연결하기
Agenda 라이브러리를 사용하기 전에 Agenda 라이브러리를 사용하려면, MongoDB 데이터베이스를 사용해야 합니다. 여기서 MongoDB를 어떻게 사용해야 할지에 대해서 살펴보는 것이 아닌, MongoDB를 어떻게 연결할 것인지 중점적으로 살펴보겠습니다.
먼저 MongoDB를 사용하려면, 아래의 링크로 들어가야 합니다.
Atlas에서는 한 프로젝트 당 하나의 cluster를 무료로 사용할 수 있습니다. 무료로 제공되는 Cluster Tier의 Stroage 용량은 512MB입니다. 본격적으로 몽고 디비의 연결을 시작해보겠습니다.
1. Database Access
데이터베이스 권한을 설정해줍니다.
Add new Database User를 누른 뒤 user Id와 password를 누른 뒤 Read Write to any database로 바꾸고 생성해줍니다.
2. Network Access
데이터베이스를 접근할 IP 주소를 Configure 해야 합니다.
ALLOW ACCESS FROM ANYWHERE를 눌러주어 모두가 접근 가능하게 바꿔줘도 되겠지만, 여기서는 서버의 아이피만 접근 가능하게 설정해주는 것이 좋습니다.
3. Connect to Project
그 후 Project나 MySQL의 workbench 역할을 하는 MongoDB Compass에 연결할 수 있습니다.
Connect using MongoDB Compass를 누릅니다.
Copy를 누르고 Compass에 들어가면 알아서 매핑해줍니다. 아까 1번에서 생성했던 User Permission 비밀번호를 입력하면 됩니다.
일단 MongoDB Compass까지 연결됐다면, 이제 웹서버에 MongoDB를 연결할 차례입니다. node.js에서 MongoDB를 사용하려면, mongoose라는 라이브러리를 사용해야 합니다.
터미널에 npm install mongoose를 입력해주고, 설치가 제대로 됐는지 확인해야 합니다.
완료됐다면, 아래의 config.mongoURI 코드에 위의 Copy 값을 넣어줍니다.
import mongoose from "mongoose";
import config from "../config";
const connectDB = async () => {
try {
await mongoose.connect(config.mongoURI, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
});
console.log("Mongoose Connected ...");
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
export default connectDB;
이렇게 MongoDB를 연결했다면, 이제 Agenda를 연결할 준비가 끝났습니다. 본격적으로 Agenda에 대해 알아보겠습니다.
Agenda란 무엇인가?
특정 시간 혹은 주기로 어떤 작업을 처리해야 할 때 node-schedule을 사용했습니다. 하지만 처리해야 할 푸시 알림의 종류가 많아지면 그만큼 관리하기 복잡하고 힘들어질 수 있습니다. Agenda는 여러 개의 크론 잡을 관리하기 위한 노드 모듈입니다. 만약 node-schedule을 활용해서 푸시 알림을 보내는 로직이라면, 만약 서버가 중단된다고 가정했을 때, 앞으로 어떤 푸시 알림을 보내려고 했는지를 모두 까먹게 됩니다. 하지만 agenda를 사용하면 mongoDB에 내가 앞으로 보내야 할 푸시 알림 들을 모두 기록해서, 서버가 중단되더라도, 다시 내가 앞으로 보내야 할 푸시 알림을 기억하고 보낼 수 있습니다. 그렇다면 Agenda를 어떻게 사용할 수 있을까요? 제대로 알아보겠습니다.
1. Agenda 설치
먼저 Agenda을 사용하기 위해서는 npm으로 agenda를 설치해야 합니다.
$ npm install agenda
만약 설치가 끝났다면, agenda를 사용하기 위해 위에서 설정한 MongoDB를 연결해줘야 합니다. address의 키값으로 config.mongoURI를 설정했는데, 이 부분에 위에서 설정한 mongoDB의 주소를 입력해줘야 합니다.
const agenda = new Agenda({
db: { address: config.mongoURI, options: { useNewUrlParser: true, useUnifiedTopology: true }},
name: "vote deadline queue"
});
이렇게 DB 연결이 끝났다면, 어떻게 푸시 알림을 반복해서 사용할 수 있을까요?
2. 태스크 정의
agenda.define() 함수로 태스크를 생성할 수 있습니다. 첫 번째 파라미터에 태스크 이름 'push'를 넘겨주고 두 번째 콜백 함수를 태스크로 설정했습니다.
agenda.define("push", function (job, done) {
console.log("agenda sample " + job.attrs.data.by)
done()
})
콜백 함수의 첫 번째 job 파라미터는 나중에 정의한 태스크를 구동할 때 넘겨주는 객체 정보를 포함하고 있습니다. 태스크가 완료되면 두 번째 파라미터인 done 콜백 함수를 실행합니다.
3. 예약
agenda.every() 함수로 위에서 정의한 'push' 태스크를 예약합니다. 현재 설정은 매 3초마다 'push' 태스크를 수행하도록 합니다.
agenda.every("3 seconds", "push", { by: "chris" })
첫 번째 파라미터는 크론 타임을 입력하는데 '3 seconds' 문자열을 입력할 수도 있습니다. 'seconds, 'minutes', 'hours'등 문자열로 입력할 수 있습니다.
세 번째 파라미터에 객체를 넘겨주는데, 이 객체는 태스크가 수행될 때 job.attrs.data 에 할당되어 들어갑니다. 그래서 job.attrs.data.by로 접근하면 설정한 문자열인 'chris' 값을 얻을 수 있는 것입니다.
4. 실행
마지막으로 agenda.start() 함수로 실행합니다.
agenda.start()
등록한 태스크를 실행하는 것이 아니라 태스크의 예약을 실행한다고 봐야 합니다. 실제 동작은 3초 후에 일어납니다.
5. 결과
만약 실행한다면, 아래 결과를 확인할 수 있습니다.
agenda sample chris
agenda sample chris
agenda sample chris
...
또한 mongoDB를 살펴보면, 등록한 push 태스크를 확인할 수 있습니다.
{
"_id" : ObjectId("55f182936af636482289a854"),
"name" : "push",
"type" : "single",
"data" : {
"by" : "chris"
},
"priority" : 0,
"repeatInterval" : "3 seconds",
"lastModifiedBy" : null,
"nextRunAt" : ISODate("2015-09-10T13:16:15.915Z"),
"lockedAt" : ISODate("2015-09-10T13:16:13.871Z"),
"lastRunAt" : ISODate("2015-09-10T13:16:12.915Z"),
"lastFinishedAt" : ISODate("2015-09-10T13:16:12.917Z")
}
태스크 이름, 사용자 데이터, 스케줄링 정보, 다음 실행시간 등을 확인할 수 있습니다. agenda를 잘 활용한다면, 푸시 알림에 대한 job을 효율적으로 관리할 수 있습니다.
마치며
앞으로도 팀의 발전을 돕는 개발자가 되기 위해 노력하려 합니다. 팀에 필요한 부분이 무엇일지 고민하면서, 팀에 도움이 된다면, 열심히 공부해서 실무에 적용할 수 있는 개발자가 되기 위해 노력하고 싶습니다. 팀의 성장에 기여할 수 있는 개발자가 되겠습니다.
참고 및 출처
'Project > 개발 협업' 카테고리의 다른 글
[협업] 협업을 위한 JIRA 이슈 번호 커밋 메시지 자동 추가하기 (0) | 2022.03.16 |
---|---|
[협업] 협업을 위한 파이어베이스 셋팅 및 호스팅 설정하기 (feat node.js) (1) | 2021.08.16 |
[협업] 협업을 위한 swagger 설정하기 (feat node.js) (0) | 2021.04.18 |
[협업] 협업을 위한 VScode 설정하기 (0) | 2021.02.21 |
[협업] 협업을 위한 Git 명령어 가이드 (1) | 2020.12.20 |