-
Notifications
You must be signed in to change notification settings - Fork 0
로직 | FCM 알림 비동기 처리
Firebase Cloud Messaging의 약자로, 사용자에게 알림을 전송할 수 있는 교차 플랫폼 메시징 솔루션
- 디바이스 토큰 요청 후 획득
- 획득한 토큰을 서버 DB에 저장
- 필요한 알림을 푸시 서버(FCM)에 요청
- 푸시 서버(FCM)은 사용자 디바이스로 PUSH 알림 전송
'냉집사'에서는 크게 다음 종류의 알림이 있다.
- 냉장고 초대 / 탈퇴 알림
- 매일 정기 시간에 냉장고 내 식품 유통기한 임박 알림
이에 다운스트림 형태의 알림만 필요했기에, FCM에서 제공하는 XMPP와 HTTP 프로토콜 중 HTTP 프로토콜을 사용하고 있었다.
XMPP 프로토콜 | HTTP 프로토콜 | |
---|---|---|
업스트림 | O | X |
다운스트림 | O | O |
- 업스트림 : 디바이스 -> 앱 서버 알림
- 다운스트림 : 앱 서버 -> 디바이스 알림
HTTP 프로토콜은 기본적으로 동기 방식으로 작동한다. 한 사용자에게 PUSH 알림을 보내면, 정상 응답을 기다리고 이를 받으면 그 다음 사용자에게 PUSH 알림을 보낸다. 즉, 보내야 할 알림이 많아질수록 알림 전송에 굉장히 많은 시간이 소요되는 문제점이 발생한다.
냉집사에서 매일 정기 시간에 유통기한이 임박한 식품을 모든 유저에게 보내는 작업은 동기 방식으로 진행했을 때, 다음과 같은 단점이 있다고 판단했다.
- 사용자의 수가 많아질수록 알림 전송에 오랜 시간 소요
- 서버는 해당 전송이 끝날 때까지 대기함에 따라, 다른 요청 누락 및 지연 위험성
그래서 기존의 동기 방식으로 처리했던 알림 전송을 비동기 방식으로 처리하도록 변경했다. 비동기 방식은 알림 요청이 발생하면 하나의 정상 응답이 아직 돌아오지 않은 상태일지라도 계속 요청을 받아 처리한다.
즉, 작업이 독립적으로 실행되어 완료 여부를 기다리지 않고 다른 작업을 실행할 수 있기 때문에 많은 알림 요청을 보다 빠르게 처리할 수 있다.
execute()
메서드는 동기 방식으로 HTTP 요청을 처리한다. 즉, 해당 스레드에서 요청이 실행되고 끝날 때까지 다음 라인으로 넘어가지 않는다.
@NotNull
private Response sendMessage(String message) throws IOException {
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message,
MediaType.get("application/json; charset=utf-8"));
Request request = new Request.Builder()
.url(API_URL)
.post(requestBody)
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken())
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8")
.build();
return client.newCall(request).execute();
}
enqueue()
메서드는 비동기 방식으로 HTTP 요청을 처리한다. 새로운 스레드에서 요청을 처리하고, 결과는 콜백을 통해 반환되는 것이다. 실패한 경우는 onFailure을 통해 반환되고, 성공한 경우는 onResponse를 통해 결과가 반환된다.
'냉집사'에서는 실패한 경우에 따른 대응을 할 수 있도록 반환되는 결과에 대한 로그를 남기도록 구현했다.
public void sendMessage(String targetToken, String title, String body) {
String message = makeMessage(targetToken, title, body);
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message, MediaType.get(MEDIA_TYPE_JSON_UTF_8));
Request request = new Request.Builder()
.url(API_URL)
.post(requestBody)
.addHeader(HttpHeaders.AUTHORIZATION, BEARER + getAccessToken())
.addHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON_UTF_8)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
log.info("알림 실패: ", e.toString());
throw new BaseException(FIREBASE_SERVER_ERROR);
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
log.info("알림 성공: ", response.body().toString());
}
});
}