FCM으로 푸시 알림 보내기
FCM
FCM은 플랫폼의 제약 없이 메시지를 전송할 수 있는 솔루션이다. 사용 방법에 따라서 특정 디바이스 또는 다수의 디바이스(한 번 요청에 최대 500건)에 앱 알림, 마케팅 메시지 등을 보낼 수 있다. 이 솔루션을 통해서 안드로이드나 IOS등의 다양한 플랫폼을 고려하지 않고 동일하게 메시지 처리를 할 수 있다는 장점이 있다. FCM을 왜 사용하는 지 다룬 블로그 포스팅이 있어서 공유한다. ([Firebase]FCM에 대해서 알아보자)
요청 흐름은 다음과 같다.
프로젝트에 FCM 구현하기
이미 FCM 라이브러리를 구글에서 만들어서 제공하기 때문에 구현 자체는 어렵지 않았다.
사전 설정
FCM 비공개 키 파일 저장
FCM을 구현하기 위해서는 파이어베이스 콘솔로 가서 프로젝트를 만들고 프로젝트 설정의 서비스 계정에서 새 비공개 키 생성으로 json 파일 하나를 받아서 작업중인 프로젝트에 넣어줘야 한다.
나는 resources/fcm에 파일을 넣었다.
의존성 추가
// fcm
implementation group: 'com.google.firebase', name: 'firebase-admin', version: '9.4.2'
커스텀 변수 추가
설정 파일을 담고 있는 위치를 설정 파일에 저장해두었다. 사실 FCM 관련 코드 통틀어서 하나만 사용하기 때문에 해당 클래스에 작성해도 상관 없을 것 같긴하다.
fcm:
config-path: {CONFIG_PATH}
FirebaseApp
메시지 처리를 할 때 FirebaseApp
이라는 객체를 사용한다. 따라서 이를 스프링 빈으로 만들어 관리하도록 하였다.
@Configuration
public class FcmConfig {
@Value("${fcm.config-path}")
private String fcmConfigPath;
@Bean
public FirebaseApp firebaseApp() throws IOException {
GoogleCredentials credentials = GoogleCredentials
.fromStream(new ClassPathResource(fcmConfigPath).getInputStream());
FirebaseOptions options = FirebaseOptions.builder().setCredentials(credentials).build();
return FirebaseApp.initializeApp(options);
}
}
코드 작성
이제 코드를 작성해보자. 사실 구글에서 라이브러리를 잘 만들어줘서 작성할 것이 많이 없다.
컨트롤러 설계
클라이언트에서 FCM 메시지를 보내달라고 요청할 앤드포인트를 설계한다.
@RestController
@RequestMapping("/api/fcm")
@RequiredArgsConstructor
public class FcmController {
private final FcmService fcmService;
@PostMapping("/send")
public ResponseEntity<JSONResponse<Object>> pushMessage(@RequestBody FcmSendRequest request) {
fcmService.sendMessage(request.toDto());
return ResponseEntity.ok(JSONResponse.of(SuccessCode.REQUEST_SUCCESS, null));
}
}
public record FcmSendRequest(
String token,
String title,
String body
) {
public FcmInfoDto toDto() {
return new FcmInfoDto(token, title, body);
}
}
서비스 설계
@Slf4j
@Service
public class FcmService {
public void sendMessage(FcmInfoDto info) {
try {
Notification notification = Notification.builder()
.setTitle(info.title())
.setBody(info.body())
.build();
Message message = Message.builder()
.setToken(info.token())
.setNotification(notification)
.putData("title", "Fore ground Message")
.putData("body", "success")
.build();
FirebaseMessaging.getInstance().send(message);
} catch (FirebaseMessagingException e) {
e.printStackTrace();
throw new GoogleServerException(ErrorCode.GOOGLE_SERVER_ERROR);
}
}
}
public record FcmInfoDto(
String token,
String title,
String body
) {
}
서비스에서는 클라이언트에서 요청한 정보를 기반으로 Notification
과 Message
객체를 정의하고, 이를 FirebaseMessaging
객체를 통해서 보내는 것이 전부다. 라이브러리를 몰랐을 때는 요청 형식을 보고 WebClient
객체로 요청을 보내 보기도 했는데 잘 안됐다. 아무튼 여기서 중요한 것은 Notification
은 시스템 UI에 표시되는 역할이고 putData()
로 정의하는 메시지는 앱이 직접 처리하도록 한다는 것이다.
테스트
테스트용 클라이언트는 chatGPT의 힘을 빌려서 만들었다. 그냥 이런게 있다 정도로 보자.
요청 형식은 다음과 같다.
POST /api/fcm/send HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Content-Length: 85
{
"token": "exampleClientToken12345",
"title": "알림 제목 예시",
"body": "알림 내용 예시"
}
디바이스 토큰을 발급받고 요청해보면 잘 나오는 것을 볼 수 있다.
간단하게 FCM을 구현할 수 있었다. 하지만 웹 환경에서는 캐시 초기화나 브라우저 변경(크롬 → 파이어폭스) 시 디바이스 토큰을 재발급받아야 하는 불편함이 있다. 그래서 유튜브의 알림처럼 장기적인 브라우저 이용이 전제된 경우에 주로 활용될 것으로 보인다.
반면 앱 환경에서는 삭제 후 재설치할 때 디바이스 토큰을 다시 보내주면 되기 때문에 토큰 관리가 훨씬 안정적이다. 이런 이유로 FCM은 특히 앱에서 더 유용하게 사용될 것으로 판단된다.
현재는 컨트롤러와 서비스만 구현했지만, 향후 리포지토리를 추가하여 디바이스 토큰을 사용자별로 저장하고 관리하면 다수의 사용자에게 알림을 효율적으로 전송할 수 있을 것이다.
댓글남기기